GPGSAndroidSetupUI.cs 16 KB

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