OVRBundleTool.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. #if UNITY_EDITOR_WIN
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using System.ComponentModel;
  6. using System.Reflection;
  7. using System.IO;
  8. using UnityEngine;
  9. using UnityEditor;
  10. public class OVRBundleTool : EditorWindow
  11. {
  12. private static List<EditorSceneInfo> buildableScenes;
  13. private static Vector2 debugLogScroll = new Vector2(0, 0);
  14. private static bool invalidBuildableScene;
  15. private static string toolLog;
  16. private static bool useOptionalTransitionApkPackage;
  17. private static GUIStyle logBoxStyle;
  18. private static Vector2 logBoxSize;
  19. private static float logBoxSpacing = 30.0f;
  20. private bool forceRestart = false;
  21. private bool showBundleManagement = false;
  22. private bool showOther = false;
  23. // Needed to ensure that APK checking does happen during editor start up, but will still happen when the window is opened/updated
  24. private static bool panelInitialized = false;
  25. private enum ApkStatus
  26. {
  27. UNKNOWN,
  28. OK,
  29. NOT_INSTALLED,
  30. DEVICE_NOT_CONNECTED,
  31. };
  32. public enum SceneBundleStatus
  33. {
  34. [Description("Unknown")]
  35. UNKNOWN,
  36. [Description("Queued")]
  37. QUEUED,
  38. [Description("Building")]
  39. BUILDING,
  40. [Description("Done")]
  41. DONE,
  42. [Description("Transferring")]
  43. TRANSFERRING,
  44. [Description("Deployed")]
  45. DEPLOYED,
  46. };
  47. public class EditorSceneInfo
  48. {
  49. public string scenePath;
  50. public string sceneName;
  51. public SceneBundleStatus buildStatus;
  52. public EditorSceneInfo(string path, string name)
  53. {
  54. scenePath = path;
  55. sceneName = name;
  56. buildStatus = SceneBundleStatus.UNKNOWN;
  57. }
  58. }
  59. private static ApkStatus currentApkStatus;
  60. [MenuItem("Oculus/OVR Build/OVR Scene Quick Preview %l", false, 10)]
  61. static void Init()
  62. {
  63. currentApkStatus = ApkStatus.UNKNOWN;
  64. EditorWindow.GetWindow(typeof(OVRBundleTool));
  65. invalidBuildableScene = false;
  66. InitializePanel();
  67. OVRPlugin.SetDeveloperMode(OVRPlugin.Bool.True);
  68. OVRPlugin.SendEvent("oculus_bundle_tool", "show_window");
  69. }
  70. public void OnEnable()
  71. {
  72. InitializePanel();
  73. }
  74. public static void InitializePanel()
  75. {
  76. panelInitialized = true;
  77. GetScenesFromBuildSettings();
  78. EditorBuildSettings.sceneListChanged += GetScenesFromBuildSettings;
  79. }
  80. private void OnGUI()
  81. {
  82. this.titleContent.text = "OVR Scene Quick Preview";
  83. if (panelInitialized)
  84. {
  85. CheckForTransitionAPK();
  86. panelInitialized = false;
  87. }
  88. if (logBoxStyle == null)
  89. {
  90. logBoxStyle = new GUIStyle();
  91. logBoxStyle.margin.left = 5;
  92. logBoxStyle.wordWrap = true;
  93. logBoxStyle.normal.textColor = logBoxStyle.focused.textColor = EditorStyles.label.normal.textColor;
  94. logBoxStyle.richText = true;
  95. }
  96. GUILayout.Space(10.0f);
  97. GUILayout.Label("Scenes", EditorStyles.boldLabel);
  98. GUIContent buildSettingsBtnTxt = new GUIContent("Open Build Settings");
  99. if (buildableScenes == null || buildableScenes.Count == 0)
  100. {
  101. string sceneErrorMessage;
  102. if (invalidBuildableScene)
  103. {
  104. sceneErrorMessage = "Invalid scene selection. \nPlease remove OVRTransitionScene in the project's build settings.";
  105. }
  106. else
  107. {
  108. sceneErrorMessage = "No scenes detected. \nTo get started, add scenes in the project's build settings.";
  109. }
  110. GUILayout.Label(sceneErrorMessage);
  111. var buildSettingBtnRt = GUILayoutUtility.GetRect(buildSettingsBtnTxt, GUI.skin.button, GUILayout.Width(150));
  112. if (GUI.Button(buildSettingBtnRt, buildSettingsBtnTxt))
  113. {
  114. OpenBuildSettingsWindow();
  115. }
  116. }
  117. else
  118. {
  119. foreach (EditorSceneInfo scene in buildableScenes)
  120. {
  121. EditorGUILayout.BeginHorizontal();
  122. {
  123. EditorGUILayout.LabelField(scene.sceneName, GUILayout.ExpandWidth(true));
  124. GUILayout.FlexibleSpace();
  125. if (scene.buildStatus != SceneBundleStatus.UNKNOWN)
  126. {
  127. string status = GetEnumDescription(scene.buildStatus);
  128. EditorGUILayout.LabelField(status, GUILayout.Width(70));
  129. }
  130. }
  131. EditorGUILayout.EndHorizontal();
  132. }
  133. EditorGUILayout.BeginHorizontal();
  134. {
  135. GUIContent sceneBtnTxt = new GUIContent("Build and Deploy Scene(s)");
  136. var sceneBtnRt = GUILayoutUtility.GetRect(sceneBtnTxt, GUI.skin.button, GUILayout.Width(200));
  137. if (GUI.Button(sceneBtnRt, sceneBtnTxt))
  138. {
  139. // Check the latest transition apk status
  140. CheckForTransitionAPK();
  141. // Show a dialog to prompt for building and deploying transition APK
  142. if (currentApkStatus != ApkStatus.OK &&
  143. EditorUtility.DisplayDialog("Build and Deploy OVR Transition APK?",
  144. "OVR Transition APK status not ready, it is required to load your scene bundle for quick preview.",
  145. "Yes",
  146. "No"))
  147. {
  148. PrintLog("Building OVR Transition APK");
  149. OVRBundleManager.BuildDeployTransitionAPK(useOptionalTransitionApkPackage);
  150. CheckForTransitionAPK();
  151. }
  152. for (int i = 0; i < buildableScenes.Count; i++)
  153. {
  154. buildableScenes[i].buildStatus = SceneBundleStatus.QUEUED;
  155. }
  156. OVRBundleManager.BuildDeployScenes(buildableScenes, forceRestart);
  157. }
  158. GUIContent forceRestartLabel = new GUIContent("Force Restart [?]", "Relaunch the application after scene bundles are finished deploying.");
  159. forceRestart = GUILayout.Toggle(forceRestart, forceRestartLabel, GUILayout.ExpandWidth(true));
  160. }
  161. EditorGUILayout.EndHorizontal();
  162. }
  163. GUILayout.Space(10.0f);
  164. GUIContent transitionContent = new GUIContent("Transition APK [?]", "Build and deploy an APK that will transition into the scene you are working on. This enables fast iteration on a specific scene.");
  165. GUILayout.Label(transitionContent, EditorStyles.boldLabel);
  166. EditorGUILayout.BeginHorizontal();
  167. {
  168. GUIStyle statusStyle = EditorStyles.label;
  169. statusStyle.richText = true;
  170. GUILayout.Label("Status: ", statusStyle, GUILayout.ExpandWidth(false));
  171. string statusMesssage;
  172. switch (currentApkStatus)
  173. {
  174. case ApkStatus.OK:
  175. statusMesssage = "<color=green>APK installed. Ready to build and deploy scenes.</color>";
  176. break;
  177. case ApkStatus.NOT_INSTALLED:
  178. statusMesssage = "<color=red>APK not installed. Press build and deploy to install the transition APK.</color>";
  179. break;
  180. case ApkStatus.DEVICE_NOT_CONNECTED:
  181. statusMesssage = "<color=red>Device not connected via ADB. Please connect device and allow debugging.</color>";
  182. break;
  183. case ApkStatus.UNKNOWN:
  184. default:
  185. statusMesssage = "<color=red>Failed to get APK status!</color>";
  186. break;
  187. }
  188. GUILayout.Label(statusMesssage, statusStyle, GUILayout.ExpandWidth(true));
  189. }
  190. EditorGUILayout.EndHorizontal();
  191. EditorGUILayout.BeginHorizontal();
  192. {
  193. GUIContent btnTxt = new GUIContent("Build and Deploy App");
  194. var rt = GUILayoutUtility.GetRect(btnTxt, GUI.skin.button, GUILayout.Width(200));
  195. if (GUI.Button(rt, btnTxt))
  196. {
  197. OVRBundleManager.BuildDeployTransitionAPK(useOptionalTransitionApkPackage);
  198. CheckForTransitionAPK();
  199. }
  200. }
  201. EditorGUILayout.EndHorizontal();
  202. GUILayout.Space(10.0f);
  203. GUILayout.Label("Utilities", EditorStyles.boldLabel);
  204. showBundleManagement = EditorGUILayout.Foldout(showBundleManagement, "Bundle Management");
  205. if (showBundleManagement)
  206. {
  207. EditorGUILayout.BeginHorizontal();
  208. {
  209. GUIContent clearDeviceBundlesTxt = new GUIContent("Delete Device Bundles");
  210. var clearDeviceBundlesBtnRt = GUILayoutUtility.GetRect(clearDeviceBundlesTxt, GUI.skin.button, GUILayout.ExpandWidth(true));
  211. if (GUI.Button(clearDeviceBundlesBtnRt, clearDeviceBundlesTxt))
  212. {
  213. OVRBundleManager.DeleteRemoteAssetBundles();
  214. }
  215. GUIContent clearLocalBundlesTxt = new GUIContent("Delete Local Bundles");
  216. var clearLocalBundlesBtnRt = GUILayoutUtility.GetRect(clearLocalBundlesTxt, GUI.skin.button, GUILayout.ExpandWidth(true));
  217. if (GUI.Button(clearLocalBundlesBtnRt, clearLocalBundlesTxt))
  218. {
  219. OVRBundleManager.DeleteLocalAssetBundles();
  220. }
  221. }
  222. EditorGUILayout.EndHorizontal();
  223. }
  224. showOther = EditorGUILayout.Foldout(showOther, "Other");
  225. if (showOther)
  226. {
  227. EditorGUILayout.BeginHorizontal();
  228. {
  229. GUIContent useOptionalTransitionPackageLabel = new GUIContent("Use optional APK package name [?]",
  230. "This allows both full build APK and transition APK to be installed on device. However, platform services like Entitlement check may fail.");
  231. EditorGUILayout.LabelField(useOptionalTransitionPackageLabel, GUILayout.ExpandWidth(false));
  232. bool newToggleValue = EditorGUILayout.Toggle(useOptionalTransitionApkPackage);
  233. if (newToggleValue != useOptionalTransitionApkPackage)
  234. {
  235. useOptionalTransitionApkPackage = newToggleValue;
  236. // Update transition APK status after changing package name option
  237. CheckForTransitionAPK();
  238. }
  239. }
  240. EditorGUILayout.EndHorizontal();
  241. EditorGUILayout.BeginHorizontal();
  242. {
  243. GUIContent launchBtnTxt = new GUIContent("Launch App");
  244. var launchBtnRt = GUILayoutUtility.GetRect(launchBtnTxt, GUI.skin.button, GUILayout.ExpandWidth(true));
  245. if (GUI.Button(launchBtnRt, launchBtnTxt))
  246. {
  247. OVRBundleManager.LaunchApplication();
  248. }
  249. var buildSettingBtnRt = GUILayoutUtility.GetRect(buildSettingsBtnTxt, GUI.skin.button, GUILayout.ExpandWidth(true));
  250. if (GUI.Button(buildSettingBtnRt, buildSettingsBtnTxt))
  251. {
  252. OpenBuildSettingsWindow();
  253. }
  254. GUIContent uninstallTxt = new GUIContent("Uninstall APK");
  255. var uninstallBtnRt = GUILayoutUtility.GetRect(uninstallTxt, GUI.skin.button, GUILayout.ExpandWidth(true));
  256. if (GUI.Button(uninstallBtnRt, uninstallTxt))
  257. {
  258. OVRBundleManager.UninstallAPK();
  259. CheckForTransitionAPK();
  260. }
  261. GUIContent clearLogTxt = new GUIContent("Clear Log");
  262. var clearLogBtnRt = GUILayoutUtility.GetRect(clearLogTxt, GUI.skin.button, GUILayout.ExpandWidth(true));
  263. if (GUI.Button(clearLogBtnRt, clearLogTxt))
  264. {
  265. PrintLog("", true);
  266. }
  267. }
  268. EditorGUILayout.EndHorizontal();
  269. }
  270. GUILayout.Space(10.0f);
  271. GUILayout.Label("Log", EditorStyles.boldLabel);
  272. if (!string.IsNullOrEmpty(toolLog))
  273. {
  274. debugLogScroll = EditorGUILayout.BeginScrollView(debugLogScroll, GUILayout.ExpandHeight(true));
  275. EditorGUILayout.SelectableLabel(toolLog, logBoxStyle, GUILayout.Height(logBoxSize.y + logBoxSpacing));
  276. EditorGUILayout.EndScrollView();
  277. }
  278. }
  279. private static void OpenBuildSettingsWindow()
  280. {
  281. EditorWindow.GetWindow(System.Type.GetType("UnityEditor.BuildPlayerWindow,UnityEditor"));
  282. }
  283. public static void UpdateSceneBuildStatus(SceneBundleStatus status, int index = -1)
  284. {
  285. if (index >= 0 && index < buildableScenes.Count)
  286. {
  287. buildableScenes[index].buildStatus = status;
  288. }
  289. else
  290. {
  291. // Update status for all scenes
  292. for (int i = 0; i < buildableScenes.Count; i++)
  293. {
  294. buildableScenes[i].buildStatus = status;
  295. }
  296. }
  297. }
  298. private static void GetScenesFromBuildSettings()
  299. {
  300. invalidBuildableScene = false;
  301. buildableScenes = new List<EditorSceneInfo>();
  302. for (int i = 0; i < EditorBuildSettings.scenes.Length; i++)
  303. {
  304. EditorBuildSettingsScene scene = EditorBuildSettings.scenes[i];
  305. if (scene.enabled)
  306. {
  307. if (Path.GetFileNameWithoutExtension(scene.path) != "OVRTransitionScene")
  308. {
  309. EditorSceneInfo sceneInfo = new EditorSceneInfo(scene.path, Path.GetFileNameWithoutExtension(scene.path));
  310. buildableScenes.Add(sceneInfo);
  311. }
  312. else
  313. {
  314. buildableScenes = null;
  315. invalidBuildableScene = true;
  316. return;
  317. }
  318. }
  319. }
  320. }
  321. private static void CheckForTransitionAPK()
  322. {
  323. OVRADBTool adbTool = new OVRADBTool(OVRConfig.Instance.GetAndroidSDKPath());
  324. if (adbTool.isReady)
  325. {
  326. string matchedPackageList, error;
  327. var transitionPackageName = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.Android);
  328. if (useOptionalTransitionApkPackage)
  329. {
  330. transitionPackageName += ".transition";
  331. }
  332. string[] packageCheckCommand = new string[] { "-d shell pm list package", transitionPackageName };
  333. if (adbTool.RunCommand(packageCheckCommand, null, out matchedPackageList, out error) == 0)
  334. {
  335. if (string.IsNullOrEmpty(matchedPackageList))
  336. {
  337. currentApkStatus = ApkStatus.NOT_INSTALLED;
  338. }
  339. else
  340. {
  341. // adb "list package" command returns all package names that contains the given query package name
  342. // Need to check if the transition package name is matched exactly
  343. if (matchedPackageList.Contains("package:" + transitionPackageName + "\r\n"))
  344. {
  345. if (useOptionalTransitionApkPackage)
  346. {
  347. // If optional package name is used, it is deterministic that the transition apk is installed
  348. currentApkStatus = ApkStatus.OK;
  349. }
  350. else
  351. {
  352. // get package info to check for TRANSITION_APK_VERSION_NAME
  353. string[] dumpPackageInfoCommand = new string[] { "-d shell dumpsys package", transitionPackageName };
  354. string packageInfo;
  355. if (adbTool.RunCommand(dumpPackageInfoCommand, null, out packageInfo, out error) == 0 &&
  356. !string.IsNullOrEmpty(packageInfo) &&
  357. packageInfo.Contains(OVRBundleManager.TRANSITION_APK_VERSION_NAME))
  358. {
  359. // Matched package name found, and the package info contains TRANSITION_APK_VERSION_NAME
  360. currentApkStatus = ApkStatus.OK;
  361. }
  362. else
  363. {
  364. currentApkStatus = ApkStatus.NOT_INSTALLED;
  365. }
  366. }
  367. }
  368. else
  369. {
  370. // No matached package name returned
  371. currentApkStatus = ApkStatus.NOT_INSTALLED;
  372. }
  373. }
  374. }
  375. else if (error.Contains("no devices found"))
  376. {
  377. currentApkStatus = ApkStatus.DEVICE_NOT_CONNECTED;
  378. }
  379. else
  380. {
  381. currentApkStatus = ApkStatus.UNKNOWN;
  382. }
  383. }
  384. }
  385. public static void PrintLog(string message, bool clear = false)
  386. {
  387. if (clear)
  388. {
  389. toolLog = message;
  390. }
  391. else
  392. {
  393. toolLog += message + "\n";
  394. }
  395. GUIContent logContent = new GUIContent(toolLog);
  396. logBoxSize = logBoxStyle.CalcSize(logContent);
  397. debugLogScroll.y = logBoxSize.y + logBoxSpacing;
  398. }
  399. public static void PrintError(string error = "")
  400. {
  401. if(!string.IsNullOrEmpty(error))
  402. {
  403. toolLog += "<color=red>Failed!\n</color>" + error + "\n";
  404. }
  405. else
  406. {
  407. toolLog += "<color=red>Failed! Check Unity log for more details.\n</color>";
  408. }
  409. }
  410. public static void PrintWarning(string warning)
  411. {
  412. toolLog += "<color=yellow>Warning!\n" + warning + "</color>\n";
  413. }
  414. public static void PrintSuccess()
  415. {
  416. toolLog += "<color=green>Success!</color>\n";
  417. }
  418. public static string GetEnumDescription(Enum eEnum)
  419. {
  420. Type enumType = eEnum.GetType();
  421. MemberInfo[] memberInfo = enumType.GetMember(eEnum.ToString());
  422. if (memberInfo != null && memberInfo.Length > 0)
  423. {
  424. var attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
  425. if (attrs != null && attrs.Length > 0)
  426. {
  427. return ((DescriptionAttribute)attrs[0]).Description;
  428. }
  429. }
  430. return eEnum.ToString();
  431. }
  432. public static bool GetUseOptionalTransitionApkPackage()
  433. {
  434. return useOptionalTransitionApkPackage;
  435. }
  436. }
  437. #endif