瀏覽代碼

Android cmake tools and Project Settings Tool handle android settings from PAL version of project json file (#10445)

Signed-off-by: moraaar <[email protected]>

## What does this PR do?

Added missing PAL files in AutomatedTesting project, which for android they include the android settings.

The android cmake tools expect android settings from `Platform\Android\android_project.json`, not from global `project.json` anymore. This PR fixes some few places in android deployment script that missed to change that.

Also, Project Settings Tool plugin (which adds Platform settings window to the editor) was also assuming that android settings came from global `project.json`. This tool's architecture hasn't been modified in a long time and needed some refactor, because it was assuming the data would be read/written from/to either the `project.json` or the ios plist file. This PR refactors project settings tool to allow to read/write from/to other json files per platform. So now android android_project.json is read/written correctly.

## How was this PR tested?

- Manually testing the platform settings window in the editor by checking it's reading the base, android and ios settings correctly. Testing making changes and save them. Also tested the reload button to revert changes.
- If the resources for the platform (android, ios) are missing the platform settings tool "disables" that platform (meaning they hide their tab), this worked on iOS and now this works on Android now too.
- Tested deploying android assets to device, it picks up the correct package name now from android settings.
moraaar 3 年之前
父節點
當前提交
e7d28edac1
共有 29 個文件被更改,包括 376 次插入267 次删除
  1. 1 1
      AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/pyside_utils.py
  2. 6 0
      AutomatedTesting/Platform/Android/android_project.cmake
  3. 9 0
      AutomatedTesting/Platform/Android/android_project.json
  4. 6 0
      AutomatedTesting/Platform/Linux/linux_project.cmake
  5. 3 0
      AutomatedTesting/Platform/Linux/linux_project.json
  6. 6 0
      AutomatedTesting/Platform/Mac/mac_project.cmake
  7. 3 0
      AutomatedTesting/Platform/Mac/mac_project.json
  8. 6 0
      AutomatedTesting/Platform/Windows/windows_project.cmake
  9. 3 0
      AutomatedTesting/Platform/Windows/windows_project.json
  10. 6 0
      AutomatedTesting/Platform/iOS/ios_project.cmake
  11. 3 0
      AutomatedTesting/Platform/iOS/ios_project.json
  12. 0 6
      AutomatedTesting/project.json
  13. 1 2
      Code/Editor/Plugins/ProjectSettingsTool/PlatformSettings_Base.cpp
  14. 4 4
      Code/Editor/Plugins/ProjectSettingsTool/Platforms.h
  15. 2 6
      Code/Editor/Plugins/ProjectSettingsTool/PlistDictionary.cpp
  16. 18 15
      Code/Editor/Plugins/ProjectSettingsTool/PlistDictionary.h
  17. 138 100
      Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsContainer.cpp
  18. 46 28
      Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsContainer.h
  19. 1 3
      Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsSerialization.cpp
  20. 3 3
      Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsSerialization.h
  21. 96 63
      Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsToolWindow.cpp
  22. 0 3
      Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsToolWindow.h
  23. 5 0
      Code/Editor/Plugins/ProjectSettingsTool/PropertyLinked.cpp
  24. 0 10
      Code/Framework/AzToolsFramework/Tests/AssetUtils.cpp
  25. 0 7
      Code/Tools/AssetBundler/tests/DummyProject/project.json
  26. 2 2
      Tools/LyTestTools/ly_test_tools/launchers/platforms/android/launcher.py
  27. 2 2
      cmake/Tools/Platform/Android/android_deployment.py
  28. 5 4
      cmake/Tools/Platform/Android/unit_test_android_deployment.py
  29. 1 8
      cmake/Tools/unit_test_common.py

+ 1 - 1
AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/pyside_utils.py

@@ -739,7 +739,7 @@ def trigger_context_menu_entry(widget, pattern, pos=None, index=None):
         return run_async(result)
 
 
-async def open_context_menu(widget, pos=None, index=None, timeout=1.0):
+async def open_context_menu(widget, pos=None, index=None, timeout=5.0):
     """
     Trigger a context menu event on a widget
     widget: The widget to trigger the event on

+ 6 - 0
AutomatedTesting/Platform/Android/android_project.cmake

@@ -0,0 +1,6 @@
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+

+ 9 - 0
AutomatedTesting/Platform/Android/android_project.json

@@ -0,0 +1,9 @@
+{
+    "Tags": ["Android"],
+    "android_settings" : {
+        "package_name" : "org.o3de.AutomatedTesting",
+        "version_number" : 1,
+        "version_name" : "1.0.0.0",
+        "orientation" : "landscape"
+    }
+}

+ 6 - 0
AutomatedTesting/Platform/Linux/linux_project.cmake

@@ -0,0 +1,6 @@
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+

+ 3 - 0
AutomatedTesting/Platform/Linux/linux_project.json

@@ -0,0 +1,3 @@
+{
+  "Tags": ["Linux"]
+}

+ 6 - 0
AutomatedTesting/Platform/Mac/mac_project.cmake

@@ -0,0 +1,6 @@
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+

+ 3 - 0
AutomatedTesting/Platform/Mac/mac_project.json

@@ -0,0 +1,3 @@
+{
+  "Tags": ["Mac"]
+}

+ 6 - 0
AutomatedTesting/Platform/Windows/windows_project.cmake

@@ -0,0 +1,6 @@
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+

+ 3 - 0
AutomatedTesting/Platform/Windows/windows_project.json

@@ -0,0 +1,3 @@
+{
+  "Tags": ["Windows"]
+}

+ 6 - 0
AutomatedTesting/Platform/iOS/ios_project.cmake

@@ -0,0 +1,6 @@
+# Copyright (c) Contributors to the Open 3D Engine Project.
+# For complete copyright and license terms please see the LICENSE at the root of this distribution.
+#
+# SPDX-License-Identifier: Apache-2.0 OR MIT
+#
+

+ 3 - 0
AutomatedTesting/Platform/iOS/ios_project.json

@@ -0,0 +1,3 @@
+{
+    "Tags": ["iOS"]
+}

+ 0 - 6
AutomatedTesting/project.json

@@ -4,12 +4,6 @@
     "executable_name": "AutomatedTestingLauncher",
     "modules": [],
     "project_id": "{D816AFAE-4BB7-4FEF-88F4-E2B786DCF29D}",
-    "android_settings": {
-        "package_name": "org.o3de.automatedtesting",
-        "version_number": 1,
-        "version_name": "1.0.0",
-        "orientation": "landscape"
-    },
     "engine": "o3de",
     "display_name": "AutomatedTesting",
     "icon_path": "preview.png",

+ 1 - 2
Code/Editor/Plugins/ProjectSettingsTool/PlatformSettings_Base.cpp

@@ -38,10 +38,9 @@ namespace ProjectSettingsTool
                         ->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::FileName))
                         ->Attribute(Attributes::PropertyIdentfier, Identfiers::ProjectName)
                     ->DataElement(Handlers::LinkedLineEdit, &BaseSettings::m_productName, "Product Name", "The project's user facing name.")
-                        ->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::IsNotEmpty))
                         ->Attribute(Attributes::PropertyIdentfier, Identfiers::ProductName)
                     ->DataElement(Handlers::LinkedLineEdit, &BaseSettings::m_executableName, "Executable Name", "The project launcher's name.")
-                        ->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::FileName))
+                        ->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::FileNameOrEmpty))
                         ->Attribute(Attributes::PropertyIdentfier, Identfiers::ExecutableName)
                     ->DataElement(Handlers::QValidatedLineEdit, &BaseSettings::m_projectPath, "Project Path", "The project root folder path .")
                         ->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::FileNameOrEmpty))

+ 4 - 4
Code/Editor/Plugins/ProjectSettingsTool/Platforms.h

@@ -21,8 +21,8 @@ namespace ProjectSettingsTool
 
     enum class PlatformDataType
     {
-        ProjectJson,
-        Plist,
+        ProjectJson, //!< Data comes from global project.json file
+        PlatformResource, //!< Data comes from another file, which can have different formats (json or plist)
 
         NumPlatformDataTypes
     };
@@ -36,8 +36,8 @@ namespace ProjectSettingsTool
     const Platform Platforms[static_cast<unsigned>(PlatformId::NumPlatformIds)]
     {
         Platform{ PlatformId::Base, PlatformDataType::ProjectJson },
-        Platform{ PlatformId::Android, PlatformDataType::ProjectJson },
-        Platform{ PlatformId::Ios, PlatformDataType::Plist }
+        Platform{ PlatformId::Android, PlatformDataType::PlatformResource },
+        Platform{ PlatformId::Ios, PlatformDataType::PlatformResource }
     };
     
 } // namespace ProjectSettingsTool

+ 2 - 6
Code/Editor/Plugins/ProjectSettingsTool/PlistDictionary.cpp

@@ -11,10 +11,6 @@
 
 namespace ProjectSettingsTool
 {
-    using namespace AZ;
-    using XmlDocument = rapidxml::xml_document<char>;
-    using XmlNode = rapidxml::xml_node<char>;
-
     PlistDictionary::PlistDictionary(XmlDocument* plist)
         : m_document(plist)
         , m_dict(plist->first_node("plist")->first_node("dict"))
@@ -25,7 +21,7 @@ namespace ProjectSettingsTool
     {
         return m_document->allocate_node
         (
-            rapidxml::node_element,
+            AZ::rapidxml::node_element,
             m_document->allocate_string(name),
             m_document->allocate_string(value)
         );
@@ -33,7 +29,7 @@ namespace ProjectSettingsTool
 
     XmlNode* PlistDictionary::MakeNode()
     {
-        return m_document->allocate_node(rapidxml::node_element);
+        return m_document->allocate_node(AZ::rapidxml::node_element);
     }
 
     XmlNode* PlistDictionary::GetPropertyKeyNode(const char* key)

+ 18 - 15
Code/Editor/Plugins/ProjectSettingsTool/PlistDictionary.h

@@ -12,52 +12,55 @@
 
 namespace ProjectSettingsTool
 {
+    using XmlDocument = AZ::rapidxml::xml_document<char>;
+    using XmlNode = AZ::rapidxml::xml_node<char>;
+     
     // Wraps a plist dict node with a friendly api to allow lookups and other actions in it
     class PlistDictionary
     {
     public:
         // Constructs the dictionary wrapper
-        PlistDictionary(AZ::rapidxml::xml_document<char>* plist);
+        PlistDictionary(XmlDocument* plist);
 
         // Allocates a new node with given name and value
-        AZ::rapidxml::xml_node<char>* MakeNode(const char* name, const char* value);
-        AZ::rapidxml::xml_node<char>* MakeNode();
+        XmlNode* MakeNode(const char* name, const char* value);
+        XmlNode* MakeNode();
 
         // Returns pointer to property data node
-        AZ::rapidxml::xml_node<char>* GetPropertyValueNode(const char* key);
+        XmlNode* GetPropertyValueNode(const char* key);
 
         // Creates a new property and returns pointer to its value node
-        AZ::rapidxml::xml_node<char>* AddProperty(const char* key);
+        XmlNode* AddProperty(const char* key);
         // Removes property from dictionary
         void RemoveProperty(const char* key);
 
         // Returns pointer to property data value or nullptr if it is empty
         const char* GetPropertyValue(const char* key);
-        const char* GetPropertyValue(AZ::rapidxml::xml_node<char>* node);
+        const char* GetPropertyValue(XmlNode* node);
 
         // Returns pointer to property data name or nullptr if it is empty
         const char* GetPropertyValueName(const char* key);
-        const char* GetPropertyValueName(AZ::rapidxml::xml_node<char>* node);
+        const char* GetPropertyValueName(XmlNode* node);
 
         // Changes value of property data and creates it if it doesn't exist
-        AZ::rapidxml::xml_node<char>* SetPropertyValue(const char* key, const char* newValue);
-        void SetPropertyValue(AZ::rapidxml::xml_node<char>* node, const char* newValue);
+        XmlNode* SetPropertyValue(const char* key, const char* newValue);
+        void SetPropertyValue(XmlNode* node, const char* newValue);
 
         // Changes name of property data and creates it if it doesn't exist
-        AZ::rapidxml::xml_node<char>* SetPropertyValueName(const char* key, const char* newName);
-        void SetPropertyValueName(AZ::rapidxml::xml_node<char>* node, const char* newName);
+        XmlNode* SetPropertyValueName(const char* key, const char* newName);
+        void SetPropertyValueName(XmlNode* node, const char* newName);
 
         // Checks to make sure a plist file has a valid dictionary
-        static bool ContainsValidDict(AZ::rapidxml::xml_document<char>* plist);
+        static bool ContainsValidDict(XmlDocument* plist);
 
     protected:
         // Returns pointer to property key node
-        AZ::rapidxml::xml_node<char>* GetPropertyKeyNode(const char* key);
+        XmlNode* GetPropertyKeyNode(const char* key);
 
 
         // pList dictionary is found in
-        AZ::rapidxml::xml_document<char>* m_document;
+        XmlDocument* m_document;
         // The dictionary of properties
-        AZ::rapidxml::xml_node<char>* m_dict;
+        XmlNode* m_dict;
     };
 } // namespace ProjectSettingsTool

+ 138 - 100
Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsContainer.cpp

@@ -8,6 +8,7 @@
 
 #include "ProjectSettingsContainer.h"
 
+#include <AzCore/IO/Path/Path.h>
 #include <AzCore/IO/SystemFile.h>
 #include <AzCore/JSON/prettywriter.h>
 #include <AzCore/JSON/stringbuffer.h>
@@ -18,20 +19,14 @@
 
 namespace ProjectSettingsTool
 {
-    using namespace AZ;
-    using StringOutcome = Outcome<void, AZStd::string>;
-    using XmlDocument = rapidxml::xml_document<char>;
-    using XmlNode = rapidxml::xml_node<char>;
-
-    const int xmlFlags = rapidxml::parse_doctype_node | rapidxml::parse_declaration_node | rapidxml::parse_no_data_nodes;
-
-    SettingsError::SettingsError(const AZStd::string& error, const AZStd::string& reason)
+    SettingsError::SettingsError(const AZStd::string& error, const AZStd::string& reason, bool shouldAbort)
+        : m_error(error)
+        , m_reason(reason)
+        , m_shouldAbort(shouldAbort)
     {
-        m_error = error;
-        m_reason = reason;
     }
 
-    StringOutcome WriteConfigFile(const AZStd::string& fileName, const AZStd::string& fileContents)
+    AZ::Outcome<void, AZStd::string> WriteConfigFile(const AZStd::string& fileName, const AZStd::string& fileContents)
     {
         using AZ::IO::SystemFile;
         const char* filePath = fileName.c_str();
@@ -39,9 +34,11 @@ namespace ProjectSettingsTool
         // Attempt to make file writable or check it out in source control
         if (CFileUtil::OverwriteFile(filePath))
         {
-            if (!CFileUtil::CreateDirectory(fileName.substr(0, fileName.find_last_of('/')).data()))
+            AZStd::string dir = filePath;
+            AZ::StringFunc::Path::StripFullName(dir);
+            if (!CFileUtil::CreateDirectory(dir.c_str()))
             {
-                return AZ::Failure(AZStd::string::format("Could not create the directory for file \"%s\".", filePath));
+                return AZ::Failure(AZStd::string::format("Could not create the directory for file \"%s\".", dir.c_str()));
             }
 
             SystemFile settingsFile;
@@ -63,7 +60,7 @@ namespace ProjectSettingsTool
         return AZ::Failure(AZStd::string::format("Could not check out or make file writable: \"%s\".", filePath));
     }
 
-    StringOutcome ReadConfigFile(const AZStd::string& fileName, AZStd::string& fileContents)
+    AZ::Outcome<void, AZStd::string> ReadConfigFile(const AZStd::string& fileName, AZStd::string& fileContents)
     {
         using AZ::IO::SystemFile;
         const char* filePath = fileName.c_str();
@@ -87,20 +84,29 @@ namespace ProjectSettingsTool
     }
 
 
-    ProjectSettingsContainer::ProjectSettingsContainer(const AZStd::string& projectJsonFileName, PlistInitVector& plistPaths)
+    ProjectSettingsContainer::ProjectSettingsContainer(const AZStd::string& projectJsonFileName, const PlatformResources& platformResources)
         : m_projectJson(JsonSettings{ projectJsonFileName, "", AZStd::make_unique<rapidjson::Document>() })
     {
-        LoadProjectJsonData();
+        LoadJson(m_projectJson);
 
-        for (PlatformAndPath& plistInfo : plistPaths)
+        for (const auto& [platformId, path] : platformResources)
         {
-            m_pListsMap.insert(AZStd::pair<PlatformId, PlistSettings>(
-                plistInfo.first,
-                PlistSettings{ plistInfo.second, "", AZStd::make_unique<XmlDocument>() }));
+            if (AZ::IO::PathView(path).Extension() == ".json")
+            {
+                m_platformSettingsMap.insert(AZStd::pair<PlatformId, PlatformSettings>(
+                    platformId,
+                    JsonSettings{ path, "", AZStd::make_unique<rapidjson::Document>() }));
+
+                LoadJson(AZStd::get<JsonSettings>(m_platformSettingsMap[platformId]));
+            }
+            else
+            {
+                m_platformSettingsMap.insert(AZStd::pair<PlatformId, PlatformSettings>(
+                    platformId,
+                    PlistSettings{ path, "", AZStd::make_unique<XmlDocument>() }));
 
-            // We will have to find it then because unique_ptrs are not copy constructible
-            // And a move constructor would copy large strings
-            LoadPlist(m_pListsMap.find(plistInfo.first)->second);
+                LoadPlist(AZStd::get<PlistSettings>(m_platformSettingsMap[platformId]));
+            }
         }
     }
 
@@ -108,15 +114,15 @@ namespace ProjectSettingsTool
     {
     }
 
-    ProjectSettingsContainer::PlistSettings* ProjectSettingsContainer::GetPlistSettingsForPlatform(const Platform& plat)
+    ProjectSettingsContainer::PlatformSettings* ProjectSettingsContainer::GetPlatformData(const Platform& plat)
     {
-        PlistSettings* result = nullptr;
+        PlatformSettings* result = nullptr;
 
-        if (plat.m_type == PlatformDataType::Plist)
+        if (plat.m_type == PlatformDataType::PlatformResource)
         {
-            auto iter = m_pListsMap.find(plat.m_id);
+            auto iter = m_platformSettingsMap.find(plat.m_id);
 
-            if (iter != m_pListsMap.end())
+            if (iter != m_platformSettingsMap.end())
             {
                 result = &iter->second;
             }
@@ -129,13 +135,13 @@ namespace ProjectSettingsTool
         return result;
     }
 
-    bool ProjectSettingsContainer::IsPlistPlatform(const Platform& plat)
+    bool ProjectSettingsContainer::HasPlatformData(const Platform& plat) const
     {
-        if (plat.m_type == PlatformDataType::Plist)
+        if (plat.m_type == PlatformDataType::PlatformResource)
         {
-            auto iter = m_pListsMap.find(plat.m_id);
+            auto iter = m_platformSettingsMap.find(plat.m_id);
 
-            if (iter != m_pListsMap.end())
+            if (iter != m_platformSettingsMap.end())
             {
                 return true;
             }
@@ -155,80 +161,104 @@ namespace ProjectSettingsTool
         return AZ::Success();
     }
 
-    void ProjectSettingsContainer::SavePlatformData(const Platform& plat)
+    void ProjectSettingsContainer::SaveSettings(const Platform& plat)
     {
-        PlistSettings* plistSettings = GetPlistSettingsForPlatform(plat);
-        if (plistSettings != nullptr)
+        const PlatformSettings* platformSettings = GetPlatformData(plat);
+        if (platformSettings != nullptr)
         {
-            SavePlist(*plistSettings);
+            if (AZStd::holds_alternative<JsonSettings>(*platformSettings))
+            {
+                SaveJson(AZStd::get<JsonSettings>(*platformSettings));
+            }
+            else
+            {
+                SavePlist(AZStd::get<PlistSettings>(*platformSettings));
+            }
         }
         else
         {
-            SaveProjectJsonData();
+            SaveJson(m_projectJson);
         }
     }
 
     void ProjectSettingsContainer::SaveProjectJsonData()
     {
-        // Needed to write a document out to a string
-        rapidjson::StringBuffer jsonDataBuffer;
-        // Use pretty writer so it can be read easier
-        rapidjson::PrettyWriter<rapidjson::StringBuffer> jsonDatawriter(jsonDataBuffer);
-
-        m_projectJson.m_document->Accept(jsonDatawriter);
-        const AZStd::string jsonDataString = jsonDataBuffer.GetString();
-
-        StringOutcome outcome = WriteConfigFile(m_projectJson.m_path, jsonDataString);
-
-        if (!outcome.IsSuccess())
-        {
-            m_errors.push(SettingsError("Failed to save project.json", outcome.GetError()));
-        }
+        SaveJson(m_projectJson);
     }
 
     void ProjectSettingsContainer::ReloadProjectJsonData()
     {
         m_projectJson.m_document.reset(new rapidjson::Document);
-        LoadProjectJsonData();
+        LoadJson(m_projectJson);
     }
 
-    void ProjectSettingsContainer::SavePlistsData()
+    void ProjectSettingsContainer::SaveAllPlatformsData()
     {
-        for (AZStd::pair<PlatformId, PlistSettings>& plist : m_pListsMap)
+        for (const auto& platformData : m_platformSettingsMap)
         {
-            LoadPlist(plist.second);
+            const PlatformSettings& platformSettings = platformData.second;
+
+            if (AZStd::holds_alternative<JsonSettings>(platformSettings))
+            {
+                SaveJson(AZStd::get<JsonSettings>(platformSettings));
+            }
+            else
+            {
+                SavePlist(AZStd::get<PlistSettings>(platformSettings));
+            }
         }
     }
 
-    void ProjectSettingsContainer::SavePlistData(const Platform& plat)
+    void ProjectSettingsContainer::SavePlatformData(const Platform& plat)
     {
-        PlistSettings* settings = GetPlistSettingsForPlatform(plat);
-        if (settings != nullptr)
+        const PlatformSettings* platformSettings = GetPlatformData(plat);
+        if (platformSettings != nullptr)
         {
-            SavePlist(*settings);
+            if (AZStd::holds_alternative<JsonSettings>(*platformSettings))
+            {
+                SaveJson(AZStd::get<JsonSettings>(*platformSettings));
+            }
+            else
+            {
+                SavePlist(AZStd::get<PlistSettings>(*platformSettings));
+            }
         }
     }
 
-    void ProjectSettingsContainer::ReloadPlistData()
+    void ProjectSettingsContainer::ReloadAllPlatformsData()
     {
-        for (AZStd::pair<PlatformId, PlistSettings>& plist : m_pListsMap)
+        for (AZStd::pair<PlatformId, PlatformSettings>& platformData : m_platformSettingsMap)
         {
-            PlistSettings& plistSettings = plist.second;
+            PlatformSettings& platformSettings = platformData.second;
 
-            plistSettings.m_document.reset(new XmlDocument());
-            LoadPlist(plistSettings);
+            if (AZStd::holds_alternative<JsonSettings>(platformSettings))
+            {
+                auto& jsonSettings = AZStd::get<JsonSettings>(platformSettings);
+                jsonSettings.m_document.reset(new rapidjson::Document);
+                LoadJson(jsonSettings);
+            }
+            else
+            {
+                auto& plistSettings = AZStd::get<PlistSettings>(platformSettings);
+                plistSettings.m_document.reset(new XmlDocument());
+                LoadPlist(plistSettings);
+            }
         }
     }
 
     rapidjson::Document& ProjectSettingsContainer::GetProjectJsonDocument()
     {
-        return *m_projectJson.m_document.get();
+        return *m_projectJson.m_document;
     }
 
     AZ::Outcome<rapidjson::Value*, void> ProjectSettingsContainer::GetProjectJsonValue(const char* key)
+    {
+        return GetJsonValue(*m_projectJson.m_document, key);
+    }
+
+    AZ::Outcome<rapidjson::Value*, void> ProjectSettingsContainer::GetJsonValue(rapidjson::Document& settings, const char* key)
     {
         // Try to find member
-        rapidjson::Document& settings = *m_projectJson.m_document;
         rapidjson::Value::MemberIterator memberIterator = settings.FindMember(key);
         if (memberIterator != settings.MemberEnd())
         {
@@ -242,38 +272,32 @@ namespace ProjectSettingsTool
         }
     }
 
-    AZStd::unique_ptr<PlistDictionary> ProjectSettingsContainer::GetPlistDictionary(const Platform& plat)
+    AZStd::unique_ptr<PlistDictionary> ProjectSettingsContainer::CreatePlistDictionary(const Platform& plat)
     {
-        if (plat.m_type == PlatformDataType::Plist)
+        if (plat.m_type == PlatformDataType::PlatformResource)
         {
-            PlistSettings* settings = GetPlistSettingsForPlatform(plat);
-            if (settings != nullptr)
+            const PlatformSettings* platformSettings = GetPlatformData(plat);
+            if (platformSettings != nullptr)
             {
+                if (!AZStd::holds_alternative<PlistSettings>(*platformSettings))
+                {
+                    AZ_Warning("ProjectSettingsContainer", false, "PlistDictionary can only be created from plist settings.");
+                    return nullptr;
+                }
 
-                if (PlistDictionary::ContainsValidDict(settings->m_document.get()))
+                const auto& plistSettings = AZStd::get<PlistSettings>(*platformSettings);
+                if (PlistDictionary::ContainsValidDict(plistSettings.m_document.get()))
                 {
-                    return AZStd::make_unique<PlistDictionary>(settings->m_document.get());
+                    return AZStd::make_unique<PlistDictionary>(plistSettings.m_document.get());
                 }
                 else
                 {
-                    //TODO: Query user if they would like to remake a valid plist then do it
-                    AZStd::string platformName;
-                    switch (plat.m_id)
-                    {
-                    case PlatformId::Ios:
-                        platformName = "iOS";
-                        break;
-                    default:
-                        platformName = "unknown";
-                        break;
-                    }
-                    AZ_Assert(false, "%s pList is in invalid state.", platformName.c_str());
+                    AZ_Error("ProjectSettingsContainer", false, "File %s contains an invalid PlistDictionary.", plistSettings.m_path.c_str());
                     return nullptr;
                 }
             }
         }
         
-        AZ_Assert(false, "This platform does not use pLists to store data.");
         return nullptr;
     }
 
@@ -282,45 +306,59 @@ namespace ProjectSettingsTool
         return m_projectJson.m_document->GetAllocator();
     }
 
-    const char* ProjectSettingsContainer::GetFailedLoadingPlistText()
-    {   
-        return "Failed to load info.plist"; 
+    void ProjectSettingsContainer::LoadJson(JsonSettings& jsonSettings)
+    {
+        AZ::Outcome<void, AZStd::string> outcome = ReadConfigFile(jsonSettings.m_path, jsonSettings.m_rawData);
+        if (!outcome.IsSuccess())
+        {
+            m_errors.push(SettingsError(AZStd::string::format("Failed to load %s", jsonSettings.m_path.c_str()), outcome.GetError(), true /*shouldAbort*/));
+        }
+
+        jsonSettings.m_document->Parse(jsonSettings.m_rawData.c_str());
     }
 
-    void ProjectSettingsContainer::LoadProjectJsonData()
+    void ProjectSettingsContainer::SaveJson(const JsonSettings& jsonSettings)
     {
-        StringOutcome outcome = ReadConfigFile(m_projectJson.m_path, m_projectJson.m_rawData);
+        // Needed to write a document out to a string
+        rapidjson::StringBuffer jsonDataBuffer;
+        // Use pretty writer so it can be read easier
+        rapidjson::PrettyWriter<rapidjson::StringBuffer> jsonDatawriter(jsonDataBuffer);
+
+        jsonSettings.m_document->Accept(jsonDatawriter);
+        const AZStd::string jsonDataString = jsonDataBuffer.GetString();
+
+        AZ::Outcome<void, AZStd::string> outcome = WriteConfigFile(jsonSettings.m_path, jsonDataString);
+
         if (!outcome.IsSuccess())
         {
-            m_errors.push(SettingsError("Failed to load project.json", outcome.GetError()));
+            m_errors.push(SettingsError(AZStd::string::format("Failed to save %s", jsonSettings.m_path.c_str()), outcome.GetError()));
         }
-
-        m_projectJson.m_document->Parse(m_projectJson.m_rawData.c_str());
     }
 
-    // Loads info.plist for iOS from disk
     void ProjectSettingsContainer::LoadPlist(PlistSettings& plistSettings)
     {
-        StringOutcome outcome = ReadConfigFile(plistSettings.m_path, plistSettings.m_rawData);
+        AZ::Outcome<void, AZStd::string> outcome = ReadConfigFile(plistSettings.m_path, plistSettings.m_rawData);
         if (!outcome.IsSuccess())
         {
-            m_errors.push(SettingsError(GetFailedLoadingPlistText(), outcome.GetError()));
+            m_errors.push(SettingsError(AZStd::string::format("Failed to load %s", plistSettings.m_path.c_str()), outcome.GetError(), true /*shouldAbort*/));
         }
 
+        const int xmlFlags = AZ::rapidxml::parse_doctype_node | AZ::rapidxml::parse_declaration_node | AZ::rapidxml::parse_no_data_nodes;
+
         plistSettings.m_document->parse<xmlFlags>(plistSettings.m_rawData.data());
     }
 
-    void ProjectSettingsContainer::SavePlist(PlistSettings& plistSettings)
+    void ProjectSettingsContainer::SavePlist(const PlistSettings& plistSettings)
     {
         // Needed to write a document out to a string
         AZStd::string xmlDocString;
-        rapidxml::print(std::back_inserter(xmlDocString), *plistSettings.m_document);
+        AZ::rapidxml::print(std::back_inserter(xmlDocString), *plistSettings.m_document);
 
-        StringOutcome outcome = WriteConfigFile(plistSettings.m_path, xmlDocString);
+        AZ::Outcome<void, AZStd::string> outcome = WriteConfigFile(plistSettings.m_path, xmlDocString);
 
         if (!outcome.IsSuccess())
         {
-            m_errors.push(SettingsError("Failed to save info.pList", outcome.GetError()));
+            m_errors.push(SettingsError(AZStd::string::format("Failed to save %s", plistSettings.m_path.c_str()), outcome.GetError()));
         }
     }
 } // namespace ProjectSettingsTool

+ 46 - 28
Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsContainer.h

@@ -17,6 +17,7 @@
 #include <AzCore/std/string/string.h>
 #include <AzCore/std/containers/queue.h>
 #include <AzCore/std/containers/unordered_map.h>
+#include <AzCore/std/containers/variant.h>
 
 #include <AzFramework/StringFunc/StringFunc.h>
 
@@ -26,89 +27,106 @@ namespace ProjectSettingsTool
 
     struct SettingsError
     {
-        SettingsError(const AZStd::string& error, const AZStd::string& reason);
+        SettingsError(const AZStd::string& error, const AZStd::string& reason, bool shouldAbort = false);
 
         // The error that occurred
         AZStd::string m_error;
+
         // The reason the error occurred
         AZStd::string m_reason;
+
+        bool m_shouldAbort = false;
     };
 
-    // Loads, saves, and provides access to all of the project settings files
+    // Loads, saves, and provides access to all of the project settings files of all platforms.
+    // It handles base settings and platform settings (android, ios) separately.
+    // For base settings it uses json document and for platform settings it uses different documents
+    // depending on the type, it supports json and plist formats.
     class ProjectSettingsContainer
     {
     public:
-        typedef AZStd::pair<PlatformId, AZStd::string> PlatformAndPath;
-        typedef AZStd::vector<PlatformAndPath> PlistInitVector;
+        using PlatformAndPath = AZStd::pair<PlatformId, AZStd::string>;
+        using PlatformResources = AZStd::vector<PlatformAndPath>;
 
         template<class DocType>
-        struct PlatformSettings
+        struct Settings
         {
             // File path to document
             AZStd::string m_path;
+
             // Raw string loaded from file
             AZStd::string m_rawData;
+
             // The document itself
             AZStd::unique_ptr<DocType> m_document;
 
         };
 
-        using JsonSettings = PlatformSettings<rapidjson::Document>;
-        using PlistSettings = PlatformSettings<AZ::rapidxml::xml_document<char>>;
+        using JsonSettings = Settings<rapidjson::Document>;
+        using PlistSettings = Settings<XmlDocument>;
+        using PlatformSettings = AZStd::variant<JsonSettings, PlistSettings>; // Platform data (Android, ios) can be either json or plist
 
         // Constructs the main manager of a document
-        ProjectSettingsContainer(const AZStd::string& projectJsonFileName, PlistInitVector& plistPaths);
+        ProjectSettingsContainer(const AZStd::string& projectJsonFileName, const PlatformResources& platformResources);
 
         // Used to destroy valueDoesNotExist
         ~ProjectSettingsContainer();
 
-        // Returns the PlistSettings for given platform
-        PlistSettings* GetPlistSettingsForPlatform(const Platform& plat);
-        // Returns true if PlistSettings are found for platform
-        bool IsPlistPlatform(const Platform& plat);
+        // Returns the PlatformSettings for given platform
+        PlatformSettings* GetPlatformData(const Platform& plat);
+
+        // Returns true if PlatformSettings are found for platform
+        bool HasPlatformData(const Platform& plat) const;
 
         // Gets the earliest error not seen
         AZ::Outcome<void, SettingsError> GetError();
-        // Save settings for given platform
-        void SavePlatformData(const Platform& plat);
+
+        // Save settings of platform data or project json data.
+        void SaveSettings(const Platform& plat);
+
         // Saves project.json to disk
         void SaveProjectJsonData();
+
         // Reloads Project.json from disk
         void ReloadProjectJsonData();
+
         // Save all pLists back to disk
-        void SavePlistsData();
-        // Save platform's plist data back to disk
-        void SavePlistData(const Platform& plat);
+        void SaveAllPlatformsData();
+
+        // Save platform's data back to disk
+        void SavePlatformData(const Platform& plat);
+
         // Reloads all plists from disk
-        void ReloadPlistData();
+        void ReloadAllPlatformsData();
+
         // Returns a reference to the project.json Document
         rapidjson::Document& GetProjectJsonDocument();
+
         // Gets reference to value in project.json
         // returns null type if not found
         AZ::Outcome<rapidjson::Value*, void> GetProjectJsonValue(const char* key);
 
-        AZStd::unique_ptr<PlistDictionary> GetPlistDictionary(const Platform& plat);
+        AZStd::unique_ptr<PlistDictionary> CreatePlistDictionary(const Platform& plat);
 
         // Returns the allocator used by ProjectJson
         rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>& GetProjectJsonAllocator();
 
-        static const char* GetFailedLoadingPlistText();
-
+        static AZ::Outcome<rapidjson::Value*, void> GetJsonValue(rapidjson::Document& settings, const char* key);
 
     protected:
-        // Loads project.json from disk
-        void LoadProjectJsonData();
-        // Loads info.plist from filePath into given document
+        void LoadJson(JsonSettings& jsonSettings);
+        void SaveJson(const JsonSettings& jsonSettings);
         void LoadPlist(PlistSettings& plistSettings);
-        void SavePlist(PlistSettings& plistSettings);
+        void SavePlist(const PlistSettings& plistSettings);
 
         // Errors that have occurred
         AZStd::queue<SettingsError> m_errors;
-        // The project.json document
+
+        // The settings from project.json (base)
         JsonSettings m_projectJson;
-        // A map to all of the loaded pLists
-        AZStd::unordered_map<PlatformId, PlistSettings> m_pListsMap;
 
+        // The settings from platform resources (android, ios)
+        AZStd::unordered_map<PlatformId, PlatformSettings> m_platformSettingsMap;
 
     private:
         AZ_DISABLE_COPY_MOVE(ProjectSettingsContainer);

+ 1 - 3
Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsSerialization.cpp

@@ -19,8 +19,6 @@
 
 namespace ProjectSettingsTool
 {
-    using XmlNode = AZ::rapidxml::xml_node<char>;
-
     const char* stringStr = "string";
     const char* arrayStr = "array";
     const char* trueStr = "true";
@@ -363,7 +361,7 @@ namespace ProjectSettingsTool
         return true;
     }
 
-    bool Serializer::UiEqualToPlistArray(AZ::rapidxml::xml_node<char>* array, AzToolsFramework::InstanceDataNode* node) const
+    bool Serializer::UiEqualToPlistArray(XmlNode* array, AzToolsFramework::InstanceDataNode* node) const
     {
         const AZ::SerializeContext::ClassData* baseMeta = node->GetClassMetadata();
         if (baseMeta)

+ 3 - 3
Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsSerialization.h

@@ -45,16 +45,16 @@ namespace ProjectSettingsTool
     protected:
         bool UiEqualToJson(rapidjson::Value* root, AzToolsFramework::InstanceDataNode* node) const;
         bool UiEqualToPlist(AzToolsFramework::InstanceDataNode* node) const;
-        bool UiEqualToPlistArray(AZ::rapidxml::xml_node<char>* array, AzToolsFramework::InstanceDataNode* node) const;
+        bool UiEqualToPlistArray(XmlNode* array, AzToolsFramework::InstanceDataNode* node) const;
         bool UiEqualToPlistImages(AzToolsFramework::InstanceDataNode* node) const;
         void LoadFromSettings(rapidjson::Value* root, AzToolsFramework::InstanceDataNode* node);
         void LoadFromSettings(AzToolsFramework::InstanceDataNode* node);
-        void LoadOrientations(AZ::rapidxml::xml_node<char>* array, AzToolsFramework::InstanceDataNode* node);
+        void LoadOrientations(XmlNode* array, AzToolsFramework::InstanceDataNode* node);
         void SetDefaults(AzToolsFramework::InstanceDataNode& node, const AZ::Uuid& type);
         void SetClassToDefaults(AzToolsFramework::InstanceDataNode* node);
         void SaveToSettings(rapidjson::Value* root, AzToolsFramework::InstanceDataNode* node);
         void SaveToSettings(AzToolsFramework::InstanceDataNode* node);
-        bool SaveOrientations(AZ::rapidxml::xml_node<char>* array, AzToolsFramework::InstanceDataNode* node);
+        bool SaveOrientations(XmlNode* array, AzToolsFramework::InstanceDataNode* node);
         void OverwriteImages(AzToolsFramework::InstanceDataNode* node);
 
         // The RPE root relative to the document's root

+ 96 - 63
Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsToolWindow.cpp

@@ -33,13 +33,23 @@
 #include <QScrollBar>
 #include <QTimer>
 
-// The object name in json for android
-const static char* androidSettings = "android_settings";
-static bool g_serializeRegistered = false;
-
 namespace ProjectSettingsTool
 {
-    using XmlNode = AZ::rapidxml::xml_node<char>;
+    namespace
+    {
+        const char* const IosSettingsPListPaths[] = {
+            "Resources/Platform/iOS/Info.plist",
+
+            // legacy paths
+            "Gem/Resources/Platform/iOS/Info.plist",
+            "Gem/Resources/IOSLauncher/Info.plist"
+        };
+
+        const char* const AndroidSettingsJsonPath = "Platform/Android/android_project.json";
+        const char* const AndroidSettingsJsonValueString = "android_settings";
+
+        bool g_serializeRegistered = false;
+    }
 
     ProjectSettingsToolWindow::ProjectSettingsToolWindow(QWidget* parent)
         : QWidget(parent)
@@ -48,27 +58,32 @@ namespace ProjectSettingsTool
         , m_reconfigureProcess()
         , m_projectRoot(GetProjectRoot())
         , m_projectName(GetProjectName())
-        , m_plistsInitVector(
-            PlatformEnabled(PlatformId::Ios) ?
-            ProjectSettingsContainer::PlistInitVector({
-                ProjectSettingsContainer::PlatformAndPath
-                { PlatformId::Ios, GetPlatformResource(PlatformId::Ios) }
-                })
-            :
-                ProjectSettingsContainer::PlistInitVector())
-        , m_settingsContainer(AZStd::make_unique<ProjectSettingsContainer>(m_projectRoot + "/project.json", m_plistsInitVector))
         , m_validator(AZStd::make_unique<Validator>())
         , m_platformProperties()
         , m_platformPropertyEditors()
         , m_propertyHandlers()
         , m_validationHandler(AZStd::make_unique<ValidationHandler>())
         , m_linkHandler(nullptr)
-        // The default path to select images at
-        , m_lastImagesPath(QStringLiteral("%1Code%2/Resources")
-            .arg(m_projectRoot.c_str()
-            , m_projectName.c_str()))
         , m_invalidState(false)
     {
+        ProjectSettingsContainer::PlatformResources platformResources;
+
+        if (PlatformEnabled(PlatformId::Ios))
+        {
+            platformResources.emplace_back(PlatformId::Ios, GetPlatformResource(PlatformId::Ios));
+        }
+
+        if (PlatformEnabled(PlatformId::Android))
+        {
+            platformResources.emplace_back(PlatformId::Android, GetPlatformResource(PlatformId::Android));
+        }
+
+        // Creates settings container to handle settings of all platforms
+        m_settingsContainer = AZStd::make_unique<ProjectSettingsContainer>(m_projectRoot + "/project.json", platformResources);
+
+        // The default path to select images at
+        m_lastImagesPath = QStringLiteral("%1Code%2/Resources").arg(m_projectRoot.c_str(), m_projectName.c_str());
+
         // Shows any and all errors that occurred during serialization with option to quit out on each one.
         ShowAllErrorsThenExitIfInvalid();
 
@@ -96,6 +111,11 @@ namespace ProjectSettingsTool
         {
             m_ui->platformTabs->removeTab(m_ui->platformTabs->indexOf(m_ui->iosTab));
         }
+        // Hide the Android tab if that platform is not enabled.
+        if (!PlatformEnabled(PlatformId::Android))
+        {
+            m_ui->platformTabs->removeTab(m_ui->platformTabs->indexOf(m_ui->androidTab));
+        }
     }
 
     ProjectSettingsToolWindow::~ProjectSettingsToolWindow()
@@ -244,17 +264,17 @@ namespace ProjectSettingsTool
     bool ProjectSettingsToolWindow::IfErrorShowThenExit()
     {
         // Grabs the earliest unseen error popping it off the error queue
-        AZ::Outcome<void, SettingsError> error = m_settingsContainer->GetError();
-        if (!error.IsSuccess())
+        AZ::Outcome<void, SettingsError> outcome = m_settingsContainer->GetError();
+        if (!outcome.IsSuccess())
         {
-            bool shouldAbort = error.GetError().m_error == m_settingsContainer->GetFailedLoadingPlistText();
+            const auto& error = outcome.GetError();
             QMessageBox::StandardButton result = QMessageBox::critical
             (
                 this,
-                error.GetError().m_error.c_str(),
-                error.GetError().m_reason.c_str(),
-                shouldAbort ? QMessageBox::Abort : QMessageBox::StandardButtons(QMessageBox::Ok | QMessageBox::Abort),
-                shouldAbort ? QMessageBox::Abort : QMessageBox::Ok
+                error.m_error.c_str(),
+                error.m_reason.c_str(),
+                error.m_shouldAbort ? QMessageBox::Abort : QMessageBox::StandardButtons(QMessageBox::Ok | QMessageBox::Abort),
+                error.m_shouldAbort ? QMessageBox::Abort : QMessageBox::Ok
             );
             if (result == QMessageBox::Abort)
             {
@@ -402,17 +422,6 @@ namespace ProjectSettingsTool
         m_platformPropertyEditors[platIdValue]->InvalidateAll();
     }
 
-    const char* GetPlatformKey(const Platform& plat)
-    {
-        switch (plat.m_id)
-        {
-        case PlatformId::Android:
-            return androidSettings;
-        default:
-            return "";
-        }
-    }
-
     void ProjectSettingsToolWindow::MakeSerializers()
     {
         for (int plat = 0; plat < static_cast<unsigned long long>(PlatformId::NumPlatformIds); ++plat)
@@ -441,20 +450,37 @@ namespace ProjectSettingsTool
                 ));
             break;
         case PlatformId::Android:
-            m_platformPropertyEditors[platIdValue]->EnumerateInstances(AZStd::bind
+        {
+            auto* androidSettings = m_settingsContainer->GetPlatformData(plat);
+            if (!androidSettings ||
+                !AZStd::holds_alternative<ProjectSettingsContainer::JsonSettings>(*androidSettings))
+            {
+                QMessageBox::critical
                 (
-                    &ProjectSettingsToolWindow::MakeSerializerJsonNonRoot,
                     this,
-                    plat,
-                    AZStd::placeholders::_1,
-                    &m_settingsContainer->GetProjectJsonDocument(),
-                    m_settingsContainer->GetProjectJsonValue(GetPlatformKey(plat)).GetValue()
-                ));
+                    "Critical",
+                    "Android settings is invalid. Project Settings Tool must close.",
+                    QMessageBox::Abort
+                );
+                ForceClose();
+            }
+            auto& androidJSonSettings = AZStd::get<ProjectSettingsContainer::JsonSettings>(*androidSettings);
+
+            m_platformPropertyEditors[platIdValue]->EnumerateInstances(AZStd::bind
+            (
+                &ProjectSettingsToolWindow::MakeSerializerJsonNonRoot,
+                this,
+                plat,
+                AZStd::placeholders::_1,
+                androidJSonSettings.m_document.get(),
+                ProjectSettingsContainer::GetJsonValue(*androidJSonSettings.m_document, AndroidSettingsJsonValueString).GetValue()
+            ));
             break;
+        }
         case PlatformId::Ios:
         {
-            PlistDictionary* dict = m_settingsContainer->GetPlistDictionary(plat).release();
-            if (dict == nullptr)
+            AZStd::unique_ptr<PlistDictionary> dict = m_settingsContainer->CreatePlistDictionary(plat);
+            if (!dict)
             {
                 QMessageBox::critical
                 (
@@ -472,8 +498,9 @@ namespace ProjectSettingsTool
                 this,
                 plat,
                 AZStd::placeholders::_1,
-                // All arguments must be copy constructible so this must be released
-                dict
+                // All arguments must be copy constructible so this must be released,
+                // MakeSerializerPlist creates a unique pointer with dict and Serializer will own it.
+                dict.release()
             ));
             break;
         }
@@ -567,9 +594,9 @@ namespace ProjectSettingsTool
                         if (needToSavePlat[plat])
                         {
                             m_platformSerializers[plat]->SaveToSettings();
-                            if (m_settingsContainer->IsPlistPlatform(platform))
+                            if (m_settingsContainer->HasPlatformData(platform))
                             {
-                                m_settingsContainer->SavePlistData(platform);
+                                m_settingsContainer->SavePlatformData(platform);
                             }
                             else
                             {
@@ -624,7 +651,7 @@ namespace ProjectSettingsTool
             if (result == QMessageBox::Reset)
             {
                 m_settingsContainer->ReloadProjectJsonData();
-                m_settingsContainer->ReloadPlistData();
+                m_settingsContainer->ReloadAllPlatformsData();
                 MakeSerializers();
 
                 // Disable links to avoid overwriting values while loading
@@ -650,6 +677,12 @@ namespace ProjectSettingsTool
             AZStd::string plistPath = GetPlatformResource(platformId);
             return !plistPath.empty();
         }
+        // Android can be disabled if the android_project.json file is missing
+        else if (platformId == PlatformId::Android)
+        {
+            const auto androidProjectJson = AZ::IO::FixedMaxPath(m_projectRoot) / AndroidSettingsJsonPath;
+            return AZ::IO::SystemFile::Exists(androidProjectJson.c_str());
+        }
 
         return true;
     }
@@ -658,25 +691,25 @@ namespace ProjectSettingsTool
     {
         if (platformId == PlatformId::Ios)
         {
-            const char* searchPaths[] = {
-                "Resources/Platform/iOS/Info.plist",
-
-                // legacy paths
-                "Gem/Resources/Platform/iOS/Info.plist",
-                "Gem/Resources/IOSLauncher/Info.plist",
-            };
-
-            for (auto relPath : searchPaths)
+            for (const auto iosSettingsPListPath : IosSettingsPListPaths)
             {
-                AZ::IO::FixedMaxPath projectPlist{ m_projectRoot };
-                projectPlist /= relPath;
+                const auto iosPList = AZ::IO::FixedMaxPath(m_projectRoot) / iosSettingsPListPath;
 
-                if (AZ::IO::SystemFile::Exists(projectPlist.c_str()))
+                if (AZ::IO::SystemFile::Exists(iosPList.c_str()))
                 {
-                    return projectPlist.LexicallyNormal().String();
+                    return iosPList.LexicallyNormal().String();
                 }
             }
         }
+        else if (platformId == PlatformId::Android)
+        {
+            const auto androidProjectJson = AZ::IO::FixedMaxPath(m_projectRoot) / AndroidSettingsJsonPath;
+
+            if (AZ::IO::SystemFile::Exists(androidProjectJson.c_str()))
+            {
+                return androidProjectJson.LexicallyNormal().String();
+            }
+        }
 
         return AZStd::string();
     }

+ 0 - 3
Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsToolWindow.h

@@ -150,9 +150,6 @@ namespace ProjectSettingsTool
         AZStd::string m_projectRoot;
         AZStd::string m_projectName;
 
-        // Used to initialize the settings container's pLists
-        ProjectSettingsContainer::PlistInitVector m_plistsInitVector;
-
         // Container to manage settings files per platform
         AZStd::unique_ptr<ProjectSettingsContainer> m_settingsContainer;
         // Allows lookup and contains all allocated QValidators

+ 5 - 0
Code/Editor/Plugins/ProjectSettingsTool/PropertyLinked.cpp

@@ -258,6 +258,11 @@ namespace ProjectSettingsTool
         for (PropertyLinkedCtrl* ctrlPointer : m_ctrlInitOrder)
         {
             auto property = m_ctrlToIdentAndLink.find(ctrlPointer);
+            // If it's not linked to another property then continue
+            if (property->second.linkedIdentifier.empty())
+            {
+                continue;
+            }
             auto link = m_identToCtrl.find(property->second.linkedIdentifier);
             if (link != m_identToCtrl.end())
             {

+ 0 - 10
Code/Framework/AzToolsFramework/Tests/AssetUtils.cpp

@@ -66,16 +66,6 @@ namespace //anonymous
     "modules" : [],
     "project_id": "{91FB81A1-072C-4A80-8FCC-7E2C4C767B4D}",
 
-    "android_settings" : {
-        "package_name" : "org.o3de.yourgame",
-        "version_number" : 1,
-        "version_name" : "1.0.0.0",
-        "orientation" : "landscape"
-    },
-
-    "provo_settings": {
-    }
-
 })";
 
     const char GemAGemFileContent[] = R"({

+ 0 - 7
Code/Tools/AssetBundler/tests/DummyProject/project.json

@@ -4,12 +4,5 @@
     "executable_name": "DummyProjectLauncher",
     "modules" : [],
     "project_id": "{91FB81A1-072C-4A80-8FCC-7E2C4C767B4D}",
-
-    "android_settings" : {
-        "package_name" : "org.o3de.yourgame",
-        "version_number" : 1,
-        "version_name" : "1.0.0.0",
-        "orientation" : "landscape"
-    },
     "engine" : "o3de"
 }

+ 2 - 2
Tools/LyTestTools/ly_test_tools/launchers/platforms/android/launcher.py

@@ -34,12 +34,12 @@ log = logging.getLogger(__name__)
 
 def get_package_name(project_path):
     """
-    Gets the Package name from the project's settings JSON.
+    Gets the Package name from the android project's settings JSON.
 
     :param project_path: The project path of the project
     :return: The Package name from the settings JSON
     """
-    project_json_path = os.path.join(project_path, 'project.json')
+    project_json_path = os.path.join(project_path, 'Platform', 'Android', 'android_project.json')
     with open(project_json_path) as json_file:
         json_list = json.loads(json_file.read())
 

+ 2 - 2
cmake/Tools/Platform/Android/android_deployment.py

@@ -130,14 +130,14 @@ class AndroidDeployment(object):
     @staticmethod
     def read_android_settings(dev_root, game_name):
         """
-        Read and parse the project.json file into a dictionary to process the specific attributes needed for the manifest template
+        Read and parse the android_project.json file into a dictionary to process the specific attributes needed for the manifest template
 
         :param dev_root:    The dev root we are working from
         :param game_name:   Name of the game under the dev root
         :return: The android settings for the game project if any
         """
         game_folder = dev_root / game_name
-        game_folder_project_properties_path = game_folder / 'project.json'
+        game_folder_project_properties_path = game_folder / 'Platform' / 'Android' / 'android_project.json'
         game_project_properties_content = game_folder_project_properties_path.resolve(strict=True)\
                                                                              .read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING,
                                                                                         errors=common.ENCODING_ERROR_HANDLINGS)

+ 5 - 4
cmake/Tools/Platform/Android/unit_test_android_deployment.py

@@ -59,12 +59,13 @@ def test_Initialize(mock_resolve_adb_tool, mock_read_android_settings):
 def test_read_android_settings(tmpdir):
 
     game_name = "Foo"
-    tmpdir.ensure(f'dev_root/{game_name}/project.json')
-    game_project_json_file = tmpdir.join(f'dev_root/{game_name}/project.json')
-    game_project_json_file.write(f'{{"android_settings": {{"game_name": "{game_name.lower()}" }} }}')
+    package_name = "o3de.org.Foo"
+    tmpdir.ensure(f'dev_root/{game_name}/Platform/Android/android_project.json')
+    game_project_json_file = tmpdir.join(f'dev_root/{game_name}/Platform/Android/android_project.json')
+    game_project_json_file.write(f'{{"android_settings": {{"package_name": "{package_name}" }} }}')
 
     result = android_deployment.AndroidDeployment.read_android_settings(pathlib.Path(tmpdir.join('dev_root').realpath()), game_name)
-    assert result['game_name'] == game_name.lower()
+    assert result['package_name'] == package_name
 
 
 def test_resolve_adb_tool(tmpdir):

+ 1 - 8
cmake/Tools/unit_test_common.py

@@ -170,14 +170,7 @@ TEST_GAME_PROJECT_JSON_FORMAT = """
     "product_name": "{project_name}",
     "executable_name": "{project_name}.GameLauncher",
     "modules" : [],
-    "project_id": "{{4F3363D3-4A7C-47A6-B464-B21524771358}}",
-
-    "android_settings" : {{
-        "package_name" : "org.o3de.yourgame",
-        "version_number" : 1,
-        "version_name" : "1.0.0.0",
-        "orientation" : "landscape"
-    }}
+    "project_id": "{{4F3363D3-4A7C-47A6-B464-B21524771358}}"
 }}
 """