diff --git a/docs/readme.md b/docs/readme.md index 7d8020bd1..32f602c17 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -81,6 +81,8 @@ Release Notes - Messaging (Android): Fix issue with `SubscribeAsync` task not completing when a cached token is available. ([#1245](https://github.com/firebase/firebase-unity-sdk/issues/1245)). + - Messaging (Android): Fix issue with missing theme causing a crash on Unity 6. + ([#1229](https://github.com/firebase/firebase-unity-sdk/issues/1229)) ### 12.9.0 - Changes diff --git a/messaging/activity/AndroidManifest.xml b/messaging/activity/AndroidManifest.xml index 407b196ed..0685cd438 100644 --- a/messaging/activity/AndroidManifest.xml +++ b/messaging/activity/AndroidManifest.xml @@ -10,6 +10,7 @@ UnityPlayerActivity to work around a known issue when receiving notification data payloads in the background. --> diff --git a/messaging/activity/FirebaseMessagingActivityGenerator.cs b/messaging/activity/FirebaseMessagingActivityGenerator.cs index 133e3a187..e2b2ea8c0 100644 --- a/messaging/activity/FirebaseMessagingActivityGenerator.cs +++ b/messaging/activity/FirebaseMessagingActivityGenerator.cs @@ -15,6 +15,7 @@ */ using System.IO; using System.Linq; +using System.Xml; using UnityEngine; using UnityEditor; using UnityEditor.Build; @@ -22,18 +23,18 @@ namespace Firebase.Messaging.Editor { -// Handles the generation of the MessagingUnityPlayerActivity java file. -// Note this regenerates the file every time an Android build occurs, -// but local changes can be preserved by using the PreserveTag below. -// This is needed because the source code needs to be present to work across -// different Unity versions, due to changes in mUnityPlayer. -// It also adjusts the base class of the file based on if GameActivity is being -// used (a new feature in Unity 2023). -public class FirebaseMessagingActivityGenerator : IPreprocessBuildWithReport { - // TODO: Ideally this should use a template file, the tricky part is locating - // the template file when it is either in the Assets path, or the Packages path. - // There are some similar cases in EDM4U, so a solution might be to use that. - private readonly string[] ActivityClassContents = new string[]{ + // Handles the generation of the MessagingUnityPlayerActivity java file. + // Note this regenerates the file every time an Android build occurs, + // but local changes can be preserved by using the PreserveTag below. + // This is needed because the source code needs to be present to work across + // different Unity versions, due to changes in mUnityPlayer. + // It also adjusts the base class of the file based on if GameActivity is being + // used (a new feature in Unity 2023). + public class FirebaseMessagingActivityGenerator : IPreprocessBuildWithReport { + // TODO: Ideally this should use a template file, the tricky part is locating + // the template file when it is either in the Assets path, or the Packages path. + // There are some similar cases in EDM4U, so a solution might be to use that. + private readonly string[] ActivityClassContents = new string[]{ "/*", " * This file is generated by the FirebaseMessagingActivityGenerator script.", " * Refer to that script for more information.", @@ -116,83 +117,155 @@ public class FirebaseMessagingActivityGenerator : IPreprocessBuildWithReport { " super.onCreate(savedInstanceState);", " }}", "}}" - }; - private readonly string BaseActivityClass = "UnityPlayerActivity"; + }; + private readonly string BaseActivityClass = "UnityPlayerActivity"; #if UNITY_2023_1_OR_NEWER - private readonly string BaseGameActivityClass = "UnityPlayerGameActivity"; + private readonly string BaseGameActivityClass = "UnityPlayerGameActivity"; #endif #if UNITY_2023_1_OR_NEWER - private readonly string UnityPlayerQuitFunction = "destroy"; + private readonly string UnityPlayerQuitFunction = "destroy"; #else - private readonly string UnityPlayerQuitFunction = "quit"; + private readonly string UnityPlayerQuitFunction = "quit"; #endif - private readonly string GeneratedFileTag = "FirebaseMessagingActivityGenerated"; - // If this tag is present on the generated file, it will not be replaced. - private readonly string PreserveTag = "FirebasePreserve"; + private readonly string GeneratedFileTag = "FirebaseMessagingActivityGenerated"; + // If this tag is present on the generated file, it will not be replaced. + private readonly string PreserveTag = "FirebasePreserve"; - private readonly string OutputPath = Path.Combine("Plugins", "Android"); - private readonly string OutputFilename = "MessagingUnityPlayerActivity.java"; + private readonly string OutputPath = Path.Combine("Plugins", "Android"); + private readonly string OutputFilename = "MessagingUnityPlayerActivity.java"; - public int callbackOrder { get { return 0; } } - public void OnPreprocessBuild(BuildReport report) { - // Only run this logic when building for Android. - if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android) { - return; - } + public int callbackOrder { get { return 0; } } + public void OnPreprocessBuild(BuildReport report) { + // Only run this logic when building for Android. + if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android) { + return; + } - // Determine what the contents of the generated file should be. - string baseClass = BaseActivityClass; + // Determine what the contents of the generated file should be. + string baseClass = BaseActivityClass; #if UNITY_2023_1_OR_NEWER - // If using the new GameActivity logic, we want to generate with that base class. - if (PlayerSettings.Android.applicationEntry.HasFlag(AndroidApplicationEntry.GameActivity)) { - baseClass = BaseGameActivityClass; + // If using the new GameActivity logic, we want to generate with that base class. + if (PlayerSettings.Android.applicationEntry.HasFlag(AndroidApplicationEntry.GameActivity)) { + baseClass = BaseGameActivityClass; + } +#endif + string fileContents = System.String.Format(System.String.Join("\n", ActivityClassContents), + baseClass, UnityPlayerQuitFunction); + + // Check if the file has already been generated. + string[] oldAssetGuids = AssetDatabase.FindAssets("l:" + GeneratedFileTag); + if (oldAssetGuids != null && oldAssetGuids.Length > 0) { + if (oldAssetGuids.Length != 1) { + Debug.LogWarning("FirebaseMessagingActivityEditor found multiple generated files with the label: " + + GeneratedFileTag + " \n" + + "No changes will be made, but this can potentially cause problems on Android with duplicate classes.\n" + + "Please check for duplicate classes, and remove any unnecessary uses of the label."); + return; + } + string oldAssetPath = AssetDatabase.GUIDToAssetPath(oldAssetGuids[0]); + Object oldAsset = AssetDatabase.LoadMainAssetAtPath(oldAssetPath); + if (oldAsset != null) { + string oldAssetFullPath = Path.Combine(Application.dataPath, "..", oldAssetPath); + string oldFileContents = System.IO.File.ReadAllText(oldAssetFullPath); + // If the old file matches what we would generate, exit early. + if (oldFileContents == fileContents) { + return; + } + // If the generated file has been tagged to be preserved, don't change it. + string[] labelList = AssetDatabase.GetLabels(oldAsset); + if (labelList.Contains(PreserveTag)) { + return; + } + // Delete the old asset. + Debug.Log("Changes detected, regenerating " + oldAssetPath + "\n" + + "To preserve local changes to that file, add the label: " + PreserveTag); + AssetDatabase.DeleteAsset(oldAssetPath); + } + } + + // Generate the new file. + string newAssetFullDirectory = Path.Combine(Application.dataPath, OutputPath); + System.IO.Directory.CreateDirectory(newAssetFullDirectory); + System.IO.File.WriteAllText(Path.Combine(newAssetFullDirectory, OutputFilename), fileContents); + string newAssetLocalPath = Path.Combine("Assets", OutputPath, OutputFilename); + AssetDatabase.ImportAsset(newAssetLocalPath); + Object newAsset = AssetDatabase.LoadMainAssetAtPath(newAssetLocalPath); + AssetDatabase.SetLabels(newAsset, new[]{GeneratedFileTag}); } + } + + // Handles adding the android:theme to the AndroidManifest file if it isn't present, + // and swaps it on Unity versions that support GameActivity, which uses a different theme + // by default. + public class FirebaseMessagingAndroidManifestModifier : IPreprocessBuildWithReport { + // Hard coded directories and file names. + private static readonly string ANDROID_MANIFEST_DIRECTORY = + Path.Combine(Path.Combine("Assets", "Plugins"), "Android"); + private static readonly string ANDROID_MANIFEST_FILE = "AndroidManifest.xml"; + private static readonly string ANDROID_MANIFEST_PATH = + Path.Combine(ANDROID_MANIFEST_DIRECTORY, ANDROID_MANIFEST_FILE); + + private static readonly string MESSAGING_ACTIVITY = "com.google.firebase.MessagingUnityPlayerActivity"; + + private static readonly string DEFAULT_THEME = "@style/UnityThemeSelector"; +#if UNITY_2023_1_OR_NEWER + private static readonly string DEFAULT_GAME_THEME = "@style/BaseUnityGameActivityTheme"; #endif - string fileContents = System.String.Format(System.String.Join("\n", ActivityClassContents), - baseClass, UnityPlayerQuitFunction); - - // Check if the file has already been generated. - string[] oldAssetGuids = AssetDatabase.FindAssets("l:" + GeneratedFileTag); - if (oldAssetGuids != null && oldAssetGuids.Length > 0) { - if (oldAssetGuids.Length != 1) { - Debug.LogWarning("FirebaseMessagingActivityEditor found multiple generated files with the label: " + - GeneratedFileTag + " \n" + - "No changes will be made, but this can potentially cause problems on Android with duplicate classes.\n" + - "Please check for duplicate classes, and remove any unnecessary uses of the label."); + + public int callbackOrder { get { return 0; } } + public void OnPreprocessBuild(BuildReport report) { + string projectDir = Path.Combine(Application.dataPath, ".."); + string manifestFile = Path.Combine(projectDir, ANDROID_MANIFEST_PATH); + + if (!File.Exists(manifestFile)) { + // Unable to locate the file, so exit early. + return; + } + + var manifest = new XmlDocument(); + manifest.Load(manifestFile); + + CheckTheme(manifest); + + manifest.Save(manifestFile); + } + + private void CheckTheme(XmlDocument manifest) { + // Create a NamespaceManager with the 'android' namespace. + var nsManager = new XmlNamespaceManager(manifest.NameTable); + var androidNamespace = manifest.DocumentElement.GetNamespaceOfPrefix("android"); + if (string.IsNullOrEmpty(androidNamespace)) { + // If unable to find the android namespace, just exit out early, since we depend on it return; } - string oldAssetPath = AssetDatabase.GUIDToAssetPath(oldAssetGuids[0]); - Object oldAsset = AssetDatabase.LoadMainAssetAtPath(oldAssetPath); - if (oldAsset != null) { - string oldAssetFullPath = Path.Combine(Application.dataPath, "..", oldAssetPath); - string oldFileContents = System.IO.File.ReadAllText(oldAssetFullPath); - // If the old file matches what we would generate, exit early. - if (oldFileContents == fileContents) { - return; + nsManager.AddNamespace("android", androidNamespace); + + // Find the activity node + XmlNode activityNode = manifest.SelectSingleNode( + $"//application/activity[@android:name='{MESSAGING_ACTIVITY}']", + nsManager); + + if (activityNode != null && activityNode is XmlElement activityElement) { + string existingTheme = activityElement.GetAttribute("theme", androidNamespace); + + string defaultTheme = DEFAULT_THEME; + bool setTheme = string.IsNullOrEmpty(existingTheme); +#if UNITY_2023_1_OR_NEWER + string otherTheme = DEFAULT_GAME_THEME; + // If using the new GameActivity logic, we want to use the default game theme. + if (PlayerSettings.Android.applicationEntry.HasFlag(AndroidApplicationEntry.GameActivity)) { + defaultTheme = DEFAULT_GAME_THEME; + otherTheme = DEFAULT_THEME; } - // If the generated file has been tagged to be preserved, don't change it. - string[] labelList = AssetDatabase.GetLabels(oldAsset); - if (labelList.Contains(PreserveTag)) { - return; + // We also want to change the theme if is is using the other default. + setTheme |= existingTheme == otherTheme; +#endif + if (setTheme) { + activityElement.SetAttribute("theme", androidNamespace, defaultTheme); } - // Delete the old asset. - Debug.Log("Changes detected, regenerating " + oldAssetPath + "\n" + - "To preserve local changes to that file, add the label: " + PreserveTag); - AssetDatabase.DeleteAsset(oldAssetPath); } } - - // Generate the new file. - string newAssetFullDirectory = Path.Combine(Application.dataPath, OutputPath); - System.IO.Directory.CreateDirectory(newAssetFullDirectory); - System.IO.File.WriteAllText(Path.Combine(newAssetFullDirectory, OutputFilename), fileContents); - string newAssetLocalPath = Path.Combine("Assets", OutputPath, OutputFilename); - AssetDatabase.ImportAsset(newAssetLocalPath); - Object newAsset = AssetDatabase.LoadMainAssetAtPath(newAssetLocalPath); - AssetDatabase.SetLabels(newAsset, new[]{GeneratedFileTag}); } -} - } // namespace Firebase.Messaging.Editor