GPGSAndroidSetupUI.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. // <copyright file="GPGSAndroidSetupUI.cs" company="Google Inc.">
  2. // Copyright (C) Google Inc. All Rights Reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. // </copyright>
  16. namespace GooglePlayGames.Editor
  17. {
  18. using System;
  19. using System.Collections;
  20. using System.IO;
  21. using System.Xml;
  22. using UnityEditor;
  23. using UnityEngine;
  24. /// <summary>
  25. /// Google Play Game Services Setup dialog for Android.
  26. /// </summary>
  27. public class GPGSAndroidSetupUI : EditorWindow
  28. {
  29. /// <summary>
  30. /// The configuration data from the play games console "resource data"
  31. /// </summary>
  32. private string mConfigData = string.Empty;
  33. /// <summary>
  34. /// The name of the class to generate containing the resource constants.
  35. /// </summary>
  36. private string mClassName = "GPGSIds";
  37. /// <summary>
  38. /// The scroll position
  39. /// </summary>
  40. private Vector2 scroll;
  41. /// <summary>
  42. /// The directory for the constants class.
  43. /// </summary>
  44. private string mConstantDirectory = "Assets";
  45. /// <summary>
  46. /// The web client identifier.
  47. /// </summary>
  48. private string mWebClientId = string.Empty;
  49. /// <summary>
  50. /// Menus the item for GPGS android setup.
  51. /// </summary>
  52. [MenuItem("Window/Google Play Games/Setup/Android setup...", false, 1)]
  53. public static void MenuItemFileGPGSAndroidSetup()
  54. {
  55. EditorWindow window = EditorWindow.GetWindow(
  56. typeof(GPGSAndroidSetupUI), true, GPGSStrings.AndroidSetup.Title);
  57. window.minSize = new Vector2(500, 400);
  58. }
  59. [MenuItem("Window/Google Play Games/Setup/Android setup...", true)]
  60. public static bool EnableAndroidMenuItem()
  61. {
  62. #if UNITY_ANDROID
  63. return true;
  64. #else
  65. return false;
  66. #endif
  67. }
  68. /// <summary>
  69. /// Performs setup using the Android resources downloaded XML file
  70. /// from the play console.
  71. /// </summary>
  72. /// <returns><c>true</c>, if setup was performed, <c>false</c> otherwise.</returns>
  73. /// <param name="clientId">The web client id.</param>
  74. /// <param name="classDirectory">the directory to write the constants file to.</param>
  75. /// <param name="className">Fully qualified class name for the resource Ids.</param>
  76. /// <param name="resourceXmlData">Resource xml data.</param>
  77. /// <param name="nearbySvcId">Nearby svc identifier.</param>
  78. /// <param name="requiresGooglePlus">Indicates this app requires G+</param>
  79. public static bool PerformSetup(
  80. string clientId,
  81. string classDirectory,
  82. string className,
  83. string resourceXmlData,
  84. string nearbySvcId)
  85. {
  86. if (string.IsNullOrEmpty(resourceXmlData) &&
  87. !string.IsNullOrEmpty(nearbySvcId))
  88. {
  89. return PerformSetup(
  90. clientId,
  91. GPGSProjectSettings.Instance.Get(GPGSUtil.APPIDKEY),
  92. nearbySvcId);
  93. }
  94. if (ParseResources(classDirectory, className, resourceXmlData))
  95. {
  96. GPGSProjectSettings.Instance.Set(GPGSUtil.CLASSDIRECTORYKEY, classDirectory);
  97. GPGSProjectSettings.Instance.Set(GPGSUtil.CLASSNAMEKEY, className);
  98. GPGSProjectSettings.Instance.Set(GPGSUtil.ANDROIDRESOURCEKEY, resourceXmlData);
  99. // check the bundle id and set it if needed.
  100. CheckBundleId();
  101. GPGSUtil.CheckAndFixDependencies();
  102. GPGSUtil.CheckAndFixVersionedAssestsPaths();
  103. AssetDatabase.Refresh();
  104. Google.VersionHandler.VerboseLoggingEnabled = true;
  105. Google.VersionHandler.UpdateVersionedAssets(forceUpdate: true);
  106. Google.VersionHandler.Enabled = true;
  107. AssetDatabase.Refresh();
  108. Google.VersionHandler.InvokeStaticMethod(
  109. Google.VersionHandler.FindClass(
  110. "Google.JarResolver",
  111. "GooglePlayServices.PlayServicesResolver"),
  112. "MenuResolve", null);
  113. return PerformSetup(
  114. clientId,
  115. GPGSProjectSettings.Instance.Get(GPGSUtil.APPIDKEY),
  116. nearbySvcId);
  117. }
  118. return false;
  119. }
  120. /// <summary>
  121. /// Provide static access to setup for facilitating automated builds.
  122. /// </summary>
  123. /// <param name="webClientId">The oauth2 client id for the game. This is only
  124. /// needed if the ID Token or access token are needed.</param>
  125. /// <param name="appId">App identifier.</param>
  126. /// <param name="nearbySvcId">Optional nearby connection serviceId</param>
  127. /// <param name="requiresGooglePlus">Indicates that GooglePlus should be enabled</param>
  128. /// <returns>true if successful</returns>
  129. public static bool PerformSetup(string webClientId, string appId, string nearbySvcId)
  130. {
  131. if (!string.IsNullOrEmpty(webClientId))
  132. {
  133. if (!GPGSUtil.LooksLikeValidClientId(webClientId))
  134. {
  135. GPGSUtil.Alert(GPGSStrings.Setup.ClientIdError);
  136. return false;
  137. }
  138. string serverAppId = webClientId.Split('-')[0];
  139. if (!serverAppId.Equals(appId))
  140. {
  141. GPGSUtil.Alert(GPGSStrings.Setup.AppIdMismatch);
  142. return false;
  143. }
  144. }
  145. // check for valid app id
  146. if (!GPGSUtil.LooksLikeValidAppId(appId) && string.IsNullOrEmpty(nearbySvcId))
  147. {
  148. GPGSUtil.Alert(GPGSStrings.Setup.AppIdError);
  149. return false;
  150. }
  151. if (nearbySvcId != null)
  152. {
  153. #if UNITY_ANDROID
  154. if (!NearbyConnectionUI.PerformSetup(nearbySvcId, true))
  155. {
  156. return false;
  157. }
  158. #endif
  159. }
  160. GPGSProjectSettings.Instance.Set(GPGSUtil.APPIDKEY, appId);
  161. GPGSProjectSettings.Instance.Set(GPGSUtil.WEBCLIENTIDKEY, webClientId);
  162. GPGSProjectSettings.Instance.Save();
  163. GPGSUtil.UpdateGameInfo();
  164. // check that Android SDK is there
  165. if (!GPGSUtil.HasAndroidSdk())
  166. {
  167. Debug.LogError("Android SDK not found.");
  168. EditorUtility.DisplayDialog(
  169. GPGSStrings.AndroidSetup.SdkNotFound,
  170. GPGSStrings.AndroidSetup.SdkNotFoundBlurb,
  171. GPGSStrings.Ok);
  172. return false;
  173. }
  174. // Generate AndroidManifest.xml
  175. GPGSUtil.GenerateAndroidManifest();
  176. // refresh assets, and we're done
  177. AssetDatabase.Refresh();
  178. GPGSProjectSettings.Instance.Set(GPGSUtil.ANDROIDSETUPDONEKEY, true);
  179. GPGSProjectSettings.Instance.Save();
  180. return true;
  181. }
  182. /// <summary>
  183. /// Called when this object is enabled by Unity editor.
  184. /// </summary>
  185. public void OnEnable()
  186. {
  187. GPGSProjectSettings settings = GPGSProjectSettings.Instance;
  188. mConstantDirectory = settings.Get(GPGSUtil.CLASSDIRECTORYKEY, mConstantDirectory);
  189. mClassName = settings.Get(GPGSUtil.CLASSNAMEKEY, mClassName);
  190. mConfigData = settings.Get(GPGSUtil.ANDROIDRESOURCEKEY);
  191. mWebClientId = settings.Get(GPGSUtil.WEBCLIENTIDKEY);
  192. }
  193. /// <summary>
  194. /// Called when the GUI should be rendered.
  195. /// </summary>
  196. public void OnGUI()
  197. {
  198. GUI.skin.label.wordWrap = true;
  199. GUILayout.BeginVertical();
  200. GUIStyle link = new GUIStyle(GUI.skin.label);
  201. link.normal.textColor = new Color(0f, 0f, 1f);
  202. GUILayout.Space(10);
  203. GUILayout.Label(GPGSStrings.AndroidSetup.Blurb);
  204. if (GUILayout.Button("Open Play Games Console", link, GUILayout.ExpandWidth(false)))
  205. {
  206. Application.OpenURL("https://play.google.com/apps/publish");
  207. }
  208. Rect last = GUILayoutUtility.GetLastRect();
  209. last.y += last.height - 2;
  210. last.x += 3;
  211. last.width -= 6;
  212. last.height = 2;
  213. GUI.Box(last, string.Empty);
  214. GUILayout.Space(15);
  215. GUILayout.Label("Constants class name", EditorStyles.boldLabel);
  216. GUILayout.Label("Enter the fully qualified name of the class to create containing the constants");
  217. GUILayout.Space(10);
  218. mConstantDirectory = EditorGUILayout.TextField(
  219. "Directory to save constants",
  220. mConstantDirectory,
  221. GUILayout.MinWidth(480));
  222. mClassName = EditorGUILayout.TextField(
  223. "Constants class name",
  224. mClassName,
  225. GUILayout.MinWidth(480));
  226. GUILayout.Label("Resources Definition", EditorStyles.boldLabel);
  227. GUILayout.Label("Paste in the Android Resources from the Play Console");
  228. GUILayout.Space(10);
  229. scroll = GUILayout.BeginScrollView(scroll);
  230. mConfigData = EditorGUILayout.TextArea(
  231. mConfigData,
  232. GUILayout.MinWidth(475),
  233. GUILayout.Height(Screen.height));
  234. GUILayout.EndScrollView();
  235. GUILayout.Space(10);
  236. // Client ID field
  237. GUILayout.Label(GPGSStrings.Setup.WebClientIdTitle, EditorStyles.boldLabel);
  238. GUILayout.Label(GPGSStrings.AndroidSetup.WebClientIdBlurb);
  239. mWebClientId = EditorGUILayout.TextField(
  240. GPGSStrings.Setup.ClientId,
  241. mWebClientId,
  242. GUILayout.MinWidth(450));
  243. GUILayout.Space(10);
  244. GUILayout.FlexibleSpace();
  245. GUILayout.BeginHorizontal();
  246. GUILayout.FlexibleSpace();
  247. if (GUILayout.Button(GPGSStrings.Setup.SetupButton, GUILayout.Width(100)))
  248. {
  249. // check that the classname entered is valid
  250. try
  251. {
  252. if (GPGSUtil.LooksLikeValidPackageName(mClassName))
  253. {
  254. DoSetup();
  255. return;
  256. }
  257. }
  258. catch (Exception e)
  259. {
  260. GPGSUtil.Alert(
  261. GPGSStrings.Error,
  262. "Invalid classname: " + e.Message);
  263. }
  264. }
  265. if (GUILayout.Button("Cancel", GUILayout.Width(100)))
  266. {
  267. Close();
  268. }
  269. GUILayout.FlexibleSpace();
  270. GUILayout.EndHorizontal();
  271. GUILayout.Space(20);
  272. GUILayout.EndVertical();
  273. }
  274. /// <summary>
  275. /// Starts the setup process.
  276. /// </summary>
  277. public void DoSetup()
  278. {
  279. if (PerformSetup(mWebClientId, mConstantDirectory, mClassName, mConfigData, null))
  280. {
  281. CheckBundleId();
  282. EditorUtility.DisplayDialog(
  283. GPGSStrings.Success,
  284. GPGSStrings.AndroidSetup.SetupComplete,
  285. GPGSStrings.Ok);
  286. GPGSProjectSettings.Instance.Set(GPGSUtil.ANDROIDSETUPDONEKEY, true);
  287. Close();
  288. }
  289. else
  290. {
  291. GPGSUtil.Alert(
  292. GPGSStrings.Error,
  293. "Invalid or missing XML resource data. Make sure the data is" +
  294. " valid and contains the app_id element");
  295. }
  296. }
  297. /// <summary>
  298. /// Checks the bundle identifier.
  299. /// </summary>
  300. /// <remarks>
  301. /// Check the package id. If one is set the gpgs properties,
  302. /// and the player settings are the default or empty, set it.
  303. /// if the player settings is not the default, then prompt before
  304. /// overwriting.
  305. /// </remarks>
  306. public static void CheckBundleId()
  307. {
  308. string packageName = GPGSProjectSettings.Instance.Get(
  309. GPGSUtil.ANDROIDBUNDLEIDKEY, string.Empty);
  310. string currentId;
  311. #if UNITY_5_6_OR_NEWER
  312. currentId = PlayerSettings.GetApplicationIdentifier(
  313. BuildTargetGroup.Android);
  314. #else
  315. currentId = PlayerSettings.bundleIdentifier;
  316. #endif
  317. if (!string.IsNullOrEmpty(packageName))
  318. {
  319. if (string.IsNullOrEmpty(currentId) ||
  320. currentId == "com.Company.ProductName")
  321. {
  322. #if UNITY_5_6_OR_NEWER
  323. PlayerSettings.SetApplicationIdentifier(
  324. BuildTargetGroup.Android, packageName);
  325. #else
  326. PlayerSettings.bundleIdentifier = packageName;
  327. #endif
  328. }
  329. else if (currentId != packageName)
  330. {
  331. if (EditorUtility.DisplayDialog(
  332. "Set Bundle Identifier?",
  333. "The server configuration is using " +
  334. packageName + ", but the player settings is set to " +
  335. currentId + ".\nSet the Bundle Identifier to " +
  336. packageName + "?",
  337. "OK",
  338. "Cancel"))
  339. {
  340. #if UNITY_5_6_OR_NEWER
  341. PlayerSettings.SetApplicationIdentifier(
  342. BuildTargetGroup.Android, packageName);
  343. #else
  344. PlayerSettings.bundleIdentifier = packageName;
  345. #endif
  346. }
  347. }
  348. }
  349. else
  350. {
  351. Debug.Log("NULL package!!");
  352. }
  353. }
  354. /// <summary>
  355. /// Parses the resources xml and set the properties. Also generates the
  356. /// constants file.
  357. /// </summary>
  358. /// <returns><c>true</c>, if resources was parsed, <c>false</c> otherwise.</returns>
  359. /// <param name="classDirectory">Class directory.</param>
  360. /// <param name="className">Class name.</param>
  361. /// <param name="res">Res. the data to parse.</param>
  362. private static bool ParseResources(string classDirectory, string className, string res)
  363. {
  364. XmlTextReader reader = new XmlTextReader(new StringReader(res));
  365. bool inResource = false;
  366. string lastProp = null;
  367. Hashtable resourceKeys = new Hashtable();
  368. string appId = null;
  369. while (reader.Read())
  370. {
  371. if (reader.Name == "resources")
  372. {
  373. inResource = true;
  374. }
  375. if (inResource && reader.Name == "string")
  376. {
  377. lastProp = reader.GetAttribute("name");
  378. }
  379. else if (inResource && !string.IsNullOrEmpty(lastProp))
  380. {
  381. if (reader.HasValue)
  382. {
  383. if (lastProp == "app_id")
  384. {
  385. appId = reader.Value;
  386. GPGSProjectSettings.Instance.Set(GPGSUtil.APPIDKEY, appId);
  387. }
  388. else if (lastProp == "package_name")
  389. {
  390. GPGSProjectSettings.Instance.Set(GPGSUtil.ANDROIDBUNDLEIDKEY, reader.Value);
  391. }
  392. else
  393. {
  394. resourceKeys[lastProp] = reader.Value;
  395. }
  396. lastProp = null;
  397. }
  398. }
  399. }
  400. reader.Close();
  401. if (resourceKeys.Count > 0)
  402. {
  403. GPGSUtil.WriteResourceIds(classDirectory, className, resourceKeys);
  404. }
  405. return appId != null;
  406. }
  407. }
  408. }