diff --git a/com.unity.mobile.android-logcat/CHANGELOG.md b/com.unity.mobile.android-logcat/CHANGELOG.md index cfe34db8..900965a8 100644 --- a/com.unity.mobile.android-logcat/CHANGELOG.md +++ b/com.unity.mobile.android-logcat/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changes & Improvements: - Unity 6.0 or later is required. + - Added a Commands window, accessible via the Tools menu in the Android Logcat window. It lets you save, organize, search and run adb commands which don't have a dedicated UI in this package, such as package management, permissions, dumpsys, settings and Meta Quest properties. Commands run asynchronously so the Editor stays responsive, support `` substitution, and can be imported and exported as JSON. ## [1.4.7] - 2025-12-12 ### Fixes & Improvements diff --git a/com.unity.mobile.android-logcat/Editor/AndroidLogcatConsoleWindow.cs b/com.unity.mobile.android-logcat/Editor/AndroidLogcatConsoleWindow.cs index 5ce27cda..2bca569d 100644 --- a/com.unity.mobile.android-logcat/Editor/AndroidLogcatConsoleWindow.cs +++ b/com.unity.mobile.android-logcat/Editor/AndroidLogcatConsoleWindow.cs @@ -338,6 +338,9 @@ private void MenuToolsSelection(object userData, string[] options, int selected) case ToolsContextMenu.LayoutViewer: AndroidLogcatLayoutViewerWindow.ShowWindow(); break; + case ToolsContextMenu.Commands: + AndroidLogcatCommandsWindow.ShowWindow(); + break; case ToolsContextMenu.WindowMemory: m_Runtime.UserSettings.ExtraWindowState.Type = ExtraWindow.Memory; break; @@ -363,6 +366,7 @@ private void DoToolsGUI() contextMenu.Add(ToolsContextMenu.StacktraceUtility, "Stacktrace Utility"); if (Unsupported.IsDeveloperMode()) contextMenu.Add(ToolsContextMenu.LayoutViewer, "Experimental/Layout Viewer"); + contextMenu.Add(ToolsContextMenu.Commands, "Commands"); var b = m_Runtime.UserSettings.ExtraWindowState.Type; contextMenu.Add(ToolsContextMenu.WindowMemory, "Window/Memory", b == ExtraWindow.Memory); contextMenu.Add(ToolsContextMenu.WindowInputs, "Window/Inputs", b == ExtraWindow.Inputs); diff --git a/com.unity.mobile.android-logcat/Editor/AndroidLogcatContextMenu.cs b/com.unity.mobile.android-logcat/Editor/AndroidLogcatContextMenu.cs index 46b747e1..242eddbe 100644 --- a/com.unity.mobile.android-logcat/Editor/AndroidLogcatContextMenu.cs +++ b/com.unity.mobile.android-logcat/Editor/AndroidLogcatContextMenu.cs @@ -28,6 +28,7 @@ internal enum ToolsContextMenu OpenTerminal, StacktraceUtility, LayoutViewer, + Commands, WindowMemory, WindowInputs, WindowHidden diff --git a/com.unity.mobile.android-logcat/Editor/AndroidLogcatDevice.cs b/com.unity.mobile.android-logcat/Editor/AndroidLogcatDevice.cs index 43d3d6d2..e7459955 100644 --- a/com.unity.mobile.android-logcat/Editor/AndroidLogcatDevice.cs +++ b/com.unity.mobile.android-logcat/Editor/AndroidLogcatDevice.cs @@ -111,6 +111,16 @@ internal virtual void UninstallPackage(string packageName) { } internal virtual void KillProcess(int processId, PosixSignal signal = PosixSignal.SIGNONE) { } internal virtual void KillProcess(string packageName, int processId, PosixSignal signal = PosixSignal.SIGNONE) { } + /// + /// Runs an arbitrary adb command against this device asynchronously. + /// The device is targeted with -s automatically unless the caller already specified one. + /// is invoked on the main thread. + /// + internal virtual void RunAdbCommandAsync(AndroidLogcatDispatcher dispatcher, string arguments, Action onComplete) + { + onComplete?.Invoke(AndroidLogcatCommandResult.CreateFailure(arguments, "No device selected.")); + } + internal bool SupportsFilteringByPid { get { return OSVersion >= kAndroidVersion70; } @@ -471,6 +481,48 @@ internal override void UninstallPackage(string packageName) m_ADB.Run(args, $"Failed to uninstall package '{packageName}'"); } + /// + /// Runs an arbitrary adb command against this device on the dispatcher's worker thread, so a + /// slow or hanging command cannot block the Editor's main thread. + /// + internal override void RunAdbCommandAsync(AndroidLogcatDispatcher dispatcher, string arguments, Action onComplete) + { + if (dispatcher == null) + { + onComplete?.Invoke(AndroidLogcatCommandResult.CreateFailure(arguments, "Dispatcher is not available.")); + return; + } + + // Target this device unless the caller already picked one explicitly. + var effectiveArgs = AndroidLogcatCommandParser.SpecifiesDevice(arguments) + ? arguments + : $"-s {Id} {arguments}"; + + dispatcher.Schedule( + new AndroidLogcatTaskInput() + { + data1 = m_ADB, + data2 = effectiveArgs, + data3 = arguments + }, + (input) => + { + var inputData = (AndroidLogcatTaskInput)input; + AndroidLogcatInternalLog.Log($"adb {inputData.data2}"); + try + { + var output = inputData.data1.Run(new[] { inputData.data2 }, $"Failed to run 'adb {inputData.data3}'"); + return AndroidLogcatCommandResult.CreateSuccess(inputData.data3, output); + } + catch (Exception ex) + { + return AndroidLogcatCommandResult.CreateFailure(inputData.data3, AndroidLogcatCommandResult.Unwrap(ex).Message); + } + }, + (result) => onComplete?.Invoke((AndroidLogcatCommandResult)result), + false); + } + internal override void KillProcess(int processId, PosixSignal signal = PosixSignal.SIGNONE) { var packageName = AndroidLogcatUtilities.GetProcessNameFromPid(m_ADB, this, processId); diff --git a/com.unity.mobile.android-logcat/Editor/AndroidLogcatUserSettings.cs b/com.unity.mobile.android-logcat/Editor/AndroidLogcatUserSettings.cs index 2f7edd09..84f997aa 100644 --- a/com.unity.mobile.android-logcat/Editor/AndroidLogcatUserSettings.cs +++ b/com.unity.mobile.android-logcat/Editor/AndroidLogcatUserSettings.cs @@ -89,6 +89,15 @@ internal class QueryLayoutSettings internal string LastScreenshotSaveLocation; } + [Serializable] + internal class CommandsSettingsData + { + [SerializeField] + internal List Favorites = new List(); + [SerializeField] + internal List GeneralCommands = new List(); + } + [SerializeField] private string m_SelectedDeviceId; [SerializeField] @@ -116,6 +125,8 @@ internal class QueryLayoutSettings private QueryLayoutSettings m_QueryLayoutSettings; [SerializeField] private InputSettings m_InputSettings; + [SerializeField] + private CommandsSettingsData m_CommandsSettings; [SerializeField] private AutoScroll m_AutoScroll; @@ -178,6 +189,7 @@ public Priority SelectedPriority public ScreenCaptureSettings CaptureSettings { set => m_ScreenCaptureSettings = value; get => m_ScreenCaptureSettings; } public QueryLayoutSettings LayoutSettings { set => m_QueryLayoutSettings = value; get => m_QueryLayoutSettings; } public InputSettings DeviceInputSettings { set => m_InputSettings = value; get => m_InputSettings; } + public CommandsSettingsData CommandsSettings { set => m_CommandsSettings = value; get => m_CommandsSettings; } public AutoScroll AutoScroll { set => m_AutoScroll = value; get => m_AutoScroll; } @@ -360,6 +372,8 @@ internal void Reset() SendText = string.Empty, TargetProcess = new ProcessInformation() }; + + m_CommandsSettings = new CommandsSettingsData(); } internal void ResetCaptureVideoSettings() diff --git a/com.unity.mobile.android-logcat/Editor/AndroidTools/Shell.cs b/com.unity.mobile.android-logcat/Editor/AndroidTools/Shell.cs index 2c3ba270..b8ee9eb3 100644 --- a/com.unity.mobile.android-logcat/Editor/AndroidTools/Shell.cs +++ b/com.unity.mobile.android-logcat/Editor/AndroidTools/Shell.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Text; +using System.Threading; namespace Unity.Android.Logcat { @@ -66,7 +67,21 @@ internal static ShellReturnInfo RunProcess(string fileName, string arguments, st return RunProcess(new ShellStartInfo() { FileName = fileName, Arguments = arguments, WorkingDirectory = workingDirectory }); } + /// + /// Runs a process, killing it if it doesn't finish within . + /// Use this for commands supplied by the user, which may never exit on their own. + /// + internal static ShellReturnInfo RunProcess(string fileName, string arguments, int timeoutMs, out bool timedOut) + { + return RunProcess(new ShellStartInfo() { FileName = fileName, Arguments = arguments }, timeoutMs, out timedOut); + } + internal static ShellReturnInfo RunProcess(ShellStartInfo startInfo) + { + return RunProcess(startInfo, Timeout.Infinite, out _); + } + + internal static ShellReturnInfo RunProcess(ShellStartInfo startInfo, int timeoutMs, out bool timedOut) { Process process = new Process(); process.StartInfo.FileName = startInfo.FileName; @@ -76,12 +91,16 @@ internal static ShellReturnInfo RunProcess(ShellStartInfo startInfo) process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.CreateNoWindow = true; + // The data received handlers are invoked on threadpool threads, so all access to these + // builders is guarded by a lock - StringBuilder is not thread safe. + var streamLock = new object(); var output = new StringBuilder(); process.OutputDataReceived += new DataReceivedEventHandler((sender, e) => { if (!string.IsNullOrEmpty(e.Data)) { - output.AppendLine(e.Data); + lock (streamLock) + output.AppendLine(e.Data); } }); @@ -90,18 +109,60 @@ internal static ShellReturnInfo RunProcess(ShellStartInfo startInfo) { if (!string.IsNullOrEmpty(e.Data)) { - error.AppendLine(e.Data); + lock (streamLock) + error.AppendLine(e.Data); } }); process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); - process.WaitForExit(); - var exitCode = process.ExitCode; + + timedOut = false; + if (timeoutMs == Timeout.Infinite) + { + process.WaitForExit(); + } + else if (process.WaitForExit(timeoutMs)) + { + // WaitForExit(int) only waits for the process to exit, not for the asynchronous + // output handlers to drain. The parameterless overload does, and returns immediately + // here because the process has already exited. + process.WaitForExit(); + } + else + { + timedOut = true; + try + { + process.Kill(); + // Give the process a moment to actually die so we can read its exit code. + process.WaitForExit(1000); + } + catch (Exception) + { + // Kill can fail if the process exited between the timeout and this call, or if + // the OS denies access. Either way there's nothing useful to do about it. + } + + // Stop the readers rather than waiting for them, so a process that could not be + // killed doesn't block this thread indefinitely. + try + { + process.CancelOutputRead(); + process.CancelErrorRead(); + } + catch (InvalidOperationException) + { + // Reads were already stopped. + } + } + + var exitCode = process.HasExited ? process.ExitCode : -1; process.Close(); - return new ShellReturnInfo(startInfo, exitCode, output.ToString(), error.ToString()); + lock (streamLock) + return new ShellReturnInfo(startInfo, exitCode, output.ToString(), error.ToString()); } } } diff --git a/com.unity.mobile.android-logcat/Editor/Commands.meta b/com.unity.mobile.android-logcat/Editor/Commands.meta new file mode 100644 index 00000000..7e282815 --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/Commands.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b519f304cd5e4d978def1bb860e01a1f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.mobile.android-logcat/Editor/Commands/AndroidLogcatAdbCommandCatalog.cs b/com.unity.mobile.android-logcat/Editor/Commands/AndroidLogcatAdbCommandCatalog.cs new file mode 100644 index 00000000..5015e1c8 --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/Commands/AndroidLogcatAdbCommandCatalog.cs @@ -0,0 +1,142 @@ +namespace Unity.Android.Logcat +{ + /// + /// A catalog of ready made adb commands the user can search, save and run. + /// + /// Scope note: this catalog intentionally does NOT cover functionality the package already + /// implements as first class features. Specifically omitted: + /// - logcat streaming/filtering -> the main Android Logcat window + /// - input simulation -> AndroidLogcatInputs / Device Input window + /// - screenshot & video capture -> AndroidLogcatScreenCaptureWindow + /// - process termination -> the process list context menu + /// The catalog covers the remaining adb surface which has no dedicated UI, so it complements + /// the package rather than duplicating it. + /// + internal static class AndroidLogcatAdbCommandCatalog + { + internal static readonly AndroidLogcatCommandEntry[] All = new[] + { + // --- Device Management --- + new AndroidLogcatCommandEntry("List Devices", "adb devices", AndroidLogcatCommandCategory.DeviceManagement), + new AndroidLogcatCommandEntry("List Devices (Verbose)", "adb devices -l", AndroidLogcatCommandCategory.DeviceManagement), + new AndroidLogcatCommandEntry("Start Server", "adb start-server", AndroidLogcatCommandCategory.DeviceManagement), + new AndroidLogcatCommandEntry("Kill Server", "adb kill-server", AndroidLogcatCommandCategory.DeviceManagement), + new AndroidLogcatCommandEntry("Reboot Device", "adb reboot", AndroidLogcatCommandCategory.DeviceManagement), + new AndroidLogcatCommandEntry("Reboot to Bootloader", "adb reboot bootloader", AndroidLogcatCommandCategory.DeviceManagement), + new AndroidLogcatCommandEntry("Reboot to Recovery", "adb reboot recovery", AndroidLogcatCommandCategory.DeviceManagement), + new AndroidLogcatCommandEntry("Get Device State", "adb get-state", AndroidLogcatCommandCategory.DeviceManagement), + new AndroidLogcatCommandEntry("Get Serial Number", "adb get-serialno", AndroidLogcatCommandCategory.DeviceManagement), + + // --- Packages --- + new AndroidLogcatCommandEntry("List All Packages", "adb shell pm list packages", AndroidLogcatCommandCategory.Packages), + new AndroidLogcatCommandEntry("List Third-Party Packages", "adb shell pm list packages -3", AndroidLogcatCommandCategory.Packages), + new AndroidLogcatCommandEntry("List System Packages", "adb shell pm list packages -s", AndroidLogcatCommandCategory.Packages), + new AndroidLogcatCommandEntry("Clear App Data", "adb shell pm clear ", AndroidLogcatCommandCategory.Packages), + new AndroidLogcatCommandEntry("Force Stop App", "adb shell am force-stop ", AndroidLogcatCommandCategory.Packages), + new AndroidLogcatCommandEntry("Start Activity", "adb shell am start -n ", AndroidLogcatCommandCategory.Packages), + new AndroidLogcatCommandEntry("Start App (Launcher)", "adb shell monkey -p -c android.intent.category.LAUNCHER 1", AndroidLogcatCommandCategory.Packages), + new AndroidLogcatCommandEntry("Start App (UnityPlayerActivity)", "adb shell am start -n /com.unity3d.player.UnityPlayerActivity", AndroidLogcatCommandCategory.Packages), + new AndroidLogcatCommandEntry("Start App (GameActivity)", "adb shell am start -n /com.google.androidgamesdk.GameActivity", AndroidLogcatCommandCategory.Packages), + new AndroidLogcatCommandEntry("Uninstall App", "adb uninstall ", AndroidLogcatCommandCategory.Packages), + new AndroidLogcatCommandEntry("Install APK", "adb install ", AndroidLogcatCommandCategory.Packages), + new AndroidLogcatCommandEntry("Install APK (Replace)", "adb install -r ", AndroidLogcatCommandCategory.Packages), + new AndroidLogcatCommandEntry("Install Multiple APKs (Split)", "adb install-multiple -r ", AndroidLogcatCommandCategory.Packages), + new AndroidLogcatCommandEntry("Push OBB File", "adb push /sdcard/Android/obb//", AndroidLogcatCommandCategory.Packages), + new AndroidLogcatCommandEntry("Create OBB Directory", "adb shell mkdir -p /sdcard/Android/obb/", AndroidLogcatCommandCategory.Packages), + new AndroidLogcatCommandEntry("Dump App Info", "adb shell dumpsys package ", AndroidLogcatCommandCategory.Packages), + + // --- Permissions --- + new AndroidLogcatCommandEntry("Grant Permission", "adb shell pm grant ", AndroidLogcatCommandCategory.Permissions), + new AndroidLogcatCommandEntry("Revoke Permission", "adb shell pm revoke ", AndroidLogcatCommandCategory.Permissions), + new AndroidLogcatCommandEntry("List Granted Permissions", "adb shell dumpsys package | grep permission", AndroidLogcatCommandCategory.Permissions), + new AndroidLogcatCommandEntry("List All Permissions", "adb shell pm list permissions -g", AndroidLogcatCommandCategory.Permissions), + new AndroidLogcatCommandEntry("List Dangerous Permissions", "adb shell pm list permissions -d -g", AndroidLogcatCommandCategory.Permissions), + new AndroidLogcatCommandEntry("Reset All Permissions", "adb shell pm reset-permissions -p ", AndroidLogcatCommandCategory.Permissions), + + // --- File Transfer --- + new AndroidLogcatCommandEntry("Push File to Device", "adb push ", AndroidLogcatCommandCategory.FileTransfer), + new AndroidLogcatCommandEntry("Pull File from Device", "adb pull ", AndroidLogcatCommandCategory.FileTransfer), + new AndroidLogcatCommandEntry("List Directory", "adb shell ls -la ", AndroidLogcatCommandCategory.FileTransfer), + new AndroidLogcatCommandEntry("Remove File", "adb shell rm ", AndroidLogcatCommandCategory.FileTransfer), + new AndroidLogcatCommandEntry("Make Directory", "adb shell mkdir -p ", AndroidLogcatCommandCategory.FileTransfer), + + // --- System Info --- + new AndroidLogcatCommandEntry("Get Android Version", "adb shell getprop ro.build.version.release", AndroidLogcatCommandCategory.SystemInfo), + new AndroidLogcatCommandEntry("Get SDK Version", "adb shell getprop ro.build.version.sdk", AndroidLogcatCommandCategory.SystemInfo), + new AndroidLogcatCommandEntry("Get Device Model", "adb shell getprop ro.product.model", AndroidLogcatCommandCategory.SystemInfo), + new AndroidLogcatCommandEntry("Get Device Manufacturer", "adb shell getprop ro.product.manufacturer", AndroidLogcatCommandCategory.SystemInfo), + new AndroidLogcatCommandEntry("Get All Properties", "adb shell getprop", AndroidLogcatCommandCategory.SystemInfo), + new AndroidLogcatCommandEntry("Get Screen Resolution", "adb shell wm size", AndroidLogcatCommandCategory.SystemInfo), + new AndroidLogcatCommandEntry("Get Screen Density", "adb shell wm density", AndroidLogcatCommandCategory.SystemInfo), + new AndroidLogcatCommandEntry("Get Battery Info", "adb shell dumpsys battery", AndroidLogcatCommandCategory.SystemInfo), + new AndroidLogcatCommandEntry("Get CPU Info", "adb shell cat /proc/cpuinfo", AndroidLogcatCommandCategory.SystemInfo), + new AndroidLogcatCommandEntry("Get Memory Info", "adb shell cat /proc/meminfo", AndroidLogcatCommandCategory.SystemInfo), + new AndroidLogcatCommandEntry("Get Disk Usage", "adb shell df", AndroidLogcatCommandCategory.SystemInfo), + new AndroidLogcatCommandEntry("Get Running Processes", "adb shell ps", AndroidLogcatCommandCategory.SystemInfo), + new AndroidLogcatCommandEntry("Get Build Properties", "adb shell cat /system/build.prop", AndroidLogcatCommandCategory.SystemInfo), + + // --- Dumpsys --- + new AndroidLogcatCommandEntry("Dump Activity Stack", "adb shell dumpsys activity activities", AndroidLogcatCommandCategory.Dumpsys), + new AndroidLogcatCommandEntry("Dump Memory Info", "adb shell dumpsys meminfo", AndroidLogcatCommandCategory.Dumpsys), + new AndroidLogcatCommandEntry("Dump Window Info", "adb shell dumpsys window", AndroidLogcatCommandCategory.Dumpsys), + new AndroidLogcatCommandEntry("Dump Display Info", "adb shell dumpsys display", AndroidLogcatCommandCategory.Dumpsys), + new AndroidLogcatCommandEntry("Dump CPU Info", "adb shell dumpsys cpuinfo", AndroidLogcatCommandCategory.Dumpsys), + new AndroidLogcatCommandEntry("Dump Battery Stats", "adb shell dumpsys batterystats", AndroidLogcatCommandCategory.Dumpsys), + new AndroidLogcatCommandEntry("Dump SurfaceFlinger", "adb shell dumpsys SurfaceFlinger", AndroidLogcatCommandCategory.Dumpsys), + new AndroidLogcatCommandEntry("Dump Graphics Stats", "adb shell dumpsys gfxinfo ", AndroidLogcatCommandCategory.Dumpsys), + new AndroidLogcatCommandEntry("Dump Wifi Info", "adb shell dumpsys wifi", AndroidLogcatCommandCategory.Dumpsys), + new AndroidLogcatCommandEntry("Dump Alarm Info", "adb shell dumpsys alarm", AndroidLogcatCommandCategory.Dumpsys), + new AndroidLogcatCommandEntry("Dump Notification Info", "adb shell dumpsys notification", AndroidLogcatCommandCategory.Dumpsys), + + // --- Settings --- + new AndroidLogcatCommandEntry("Enable Stay Awake", "adb shell settings put global stay_on_while_plugged_in 3", AndroidLogcatCommandCategory.Settings), + new AndroidLogcatCommandEntry("Disable Stay Awake", "adb shell settings put global stay_on_while_plugged_in 0", AndroidLogcatCommandCategory.Settings), + new AndroidLogcatCommandEntry("Show Touches On", "adb shell settings put system show_touches 1", AndroidLogcatCommandCategory.Settings), + new AndroidLogcatCommandEntry("Show Touches Off", "adb shell settings put system show_touches 0", AndroidLogcatCommandCategory.Settings), + new AndroidLogcatCommandEntry("Enable USB Debugging", "adb shell settings put global adb_enabled 1", AndroidLogcatCommandCategory.Settings), + new AndroidLogcatCommandEntry("Set Screen Off Timeout", "adb shell settings put system screen_off_timeout ", AndroidLogcatCommandCategory.Settings), + + // --- Networking --- + new AndroidLogcatCommandEntry("Enable Network ADB", "adb tcpip 5555", AndroidLogcatCommandCategory.Networking), + new AndroidLogcatCommandEntry("Connect via IP", "adb connect :5555", AndroidLogcatCommandCategory.Networking), + new AndroidLogcatCommandEntry("Disconnect All", "adb disconnect", AndroidLogcatCommandCategory.Networking), + new AndroidLogcatCommandEntry("Forward Port", "adb forward tcp: tcp:", AndroidLogcatCommandCategory.Networking), + new AndroidLogcatCommandEntry("Reverse Port", "adb reverse tcp: tcp:", AndroidLogcatCommandCategory.Networking), + new AndroidLogcatCommandEntry("List Port Forwards", "adb forward --list", AndroidLogcatCommandCategory.Networking), + new AndroidLogcatCommandEntry("Get Network IP Address", "adb shell ip addr show wlan0", AndroidLogcatCommandCategory.Networking), + new AndroidLogcatCommandEntry("Get Network Interfaces", "adb shell ip link show", AndroidLogcatCommandCategory.Networking), + new AndroidLogcatCommandEntry("Dump Network Stats", "adb shell dumpsys netstats", AndroidLogcatCommandCategory.Networking), + new AndroidLogcatCommandEntry("Dump Network Connectivity", "adb shell dumpsys connectivity", AndroidLogcatCommandCategory.Networking), + new AndroidLogcatCommandEntry("Ping Host", "adb shell ping -c 4 ", AndroidLogcatCommandCategory.Networking), + + // --- Advanced --- + new AndroidLogcatCommandEntry("Bug Report", "adb bugreport", AndroidLogcatCommandCategory.Advanced), + new AndroidLogcatCommandEntry("Remount System", "adb remount", AndroidLogcatCommandCategory.Advanced), + new AndroidLogcatCommandEntry("Root Shell", "adb root", AndroidLogcatCommandCategory.Advanced), + new AndroidLogcatCommandEntry("Unroot Shell", "adb unroot", AndroidLogcatCommandCategory.Advanced), + new AndroidLogcatCommandEntry("Disable Verity", "adb disable-verity", AndroidLogcatCommandCategory.Advanced), + new AndroidLogcatCommandEntry("Enable Verity", "adb enable-verity", AndroidLogcatCommandCategory.Advanced), + new AndroidLogcatCommandEntry("Check Boot Completed", "adb shell getprop sys.boot_completed", AndroidLogcatCommandCategory.Advanced), + new AndroidLogcatCommandEntry("List Tombstones", "adb shell ls -lt /data/tombstones/", AndroidLogcatCommandCategory.Advanced), + new AndroidLogcatCommandEntry("Pull Tombstone", "adb pull /data/tombstones/ ", AndroidLogcatCommandCategory.Advanced), + + // --- Meta Quest / XR --- + new AndroidLogcatCommandEntry("Quest: List OVR Packages", "adb shell pm list packages | grep oculus", AndroidLogcatCommandCategory.Quest), + new AndroidLogcatCommandEntry("Quest: Get Guardian State", "adb shell dumpsys OVRGuardianService", AndroidLogcatCommandCategory.Quest), + new AndroidLogcatCommandEntry("Quest: Get Compositor Stats", "adb shell dumpsys OVRCompositor", AndroidLogcatCommandCategory.Quest), + new AndroidLogcatCommandEntry("Quest: Set GPU Level", "adb shell setprop debug.oculus.gpuLevel <0-4>", AndroidLogcatCommandCategory.Quest), + new AndroidLogcatCommandEntry("Quest: Set CPU Level", "adb shell setprop debug.oculus.cpuLevel <0-4>", AndroidLogcatCommandCategory.Quest), + new AndroidLogcatCommandEntry("Quest: Enable Perf Overlay", "adb shell setprop debug.oculus.enablePerfOverlay 1", AndroidLogcatCommandCategory.Quest), + new AndroidLogcatCommandEntry("Quest: Disable Perf Overlay", "adb shell setprop debug.oculus.enablePerfOverlay 0", AndroidLogcatCommandCategory.Quest), + new AndroidLogcatCommandEntry("Quest: Set Fixed Foveation", "adb shell setprop debug.oculus.foveation.level <0-4>", AndroidLogcatCommandCategory.Quest), + new AndroidLogcatCommandEntry("Quest: Set Refresh Rate", "adb shell setprop debug.oculus.refreshRate <72|90|120>", AndroidLogcatCommandCategory.Quest), + + // --- AAB / Bundletool --- + // Note: non-adb commands resolve relative to the Editor's working directory, so + // bundletool.jar is a placeholder to be filled in with an absolute path. + new AndroidLogcatCommandEntry("bundletool: Build Split APKs", "java -jar build-apks --bundle= --output= --connected-device", AndroidLogcatCommandCategory.Bundletool), + new AndroidLogcatCommandEntry("bundletool: Install APKs", "java -jar install-apks --apks=", AndroidLogcatCommandCategory.Bundletool), + new AndroidLogcatCommandEntry("bundletool: Get Device Spec", "java -jar get-device-spec --output=", AndroidLogcatCommandCategory.Bundletool), + }; + } +} diff --git a/com.unity.mobile.android-logcat/Editor/Commands/AndroidLogcatAdbCommandCatalog.cs.meta b/com.unity.mobile.android-logcat/Editor/Commands/AndroidLogcatAdbCommandCatalog.cs.meta new file mode 100644 index 00000000..0a6503d9 --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/Commands/AndroidLogcatAdbCommandCatalog.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c184fe6fa853c48a29bcb98002dc77fd \ No newline at end of file diff --git a/com.unity.mobile.android-logcat/Editor/Commands/AndroidLogcatAddCommandDialog.cs b/com.unity.mobile.android-logcat/Editor/Commands/AndroidLogcatAddCommandDialog.cs new file mode 100644 index 00000000..f7de4ba9 --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/Commands/AndroidLogcatAddCommandDialog.cs @@ -0,0 +1,100 @@ +using System; +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Unity.Android.Logcat +{ + internal class AndroidLogcatAddCommandDialog : EditorWindow + { + // Serialized so the dialog survives a domain reload with its contents intact. + [SerializeField] string m_Name = ""; + [SerializeField] string m_Command = ""; + [SerializeField] AndroidLogcatCommandCategory m_Category = AndroidLogcatCommandCategory.Uncategorized; + [SerializeField] bool m_IsEdit; + + // The callback cannot be serialized, so close rather than present a dialog whose Save does nothing. + Action m_OnSave; + + void OnEnable() + { + AssemblyReloadEvents.beforeAssemblyReload += Close; + } + + void OnDisable() + { + AssemblyReloadEvents.beforeAssemblyReload -= Close; + } + + /// + /// Opens the dialog. When is supplied the fields are prefilled with + /// its values and the dialog acts as an editor for that entry. + /// + internal static void Show(Action onSave, AndroidLogcatCommandEntry existing = null) + { + var wnd = CreateInstance(); + wnd.titleContent = new GUIContent(existing != null ? "Edit Command" : "Add Command"); + wnd.m_OnSave = onSave; + wnd.minSize = new Vector2(400, 200); + wnd.maxSize = new Vector2(600, 400); + + if (existing != null) + { + wnd.m_Name = existing.name ?? ""; + wnd.m_Command = existing.command ?? ""; + wnd.m_Category = existing.category; + wnd.m_IsEdit = true; + } + + // Note: the UI is built in CreateGUI, which runs after this point, so the assignments + // above are guaranteed to be visible to it. Building the UI in OnEnable would run before + // CreateInstance returns and leave the fields blank. + wnd.ShowUtility(); + } + + void CreateGUI() + { + var r = rootVisualElement; + r.Clear(); + + var tree = AndroidLogcatUtilities.LoadUXML("Command/AndroidLogcatAddCommand.uxml"); + tree.CloneTree(r); + + var nameField = r.Q("NameField"); + nameField.value = m_Name; + nameField.RegisterValueChangedCallback(evt => m_Name = evt.newValue); + + var commandField = r.Q("CommandField"); + commandField.value = m_Command; + commandField.RegisterValueChangedCallback(evt => m_Command = evt.newValue); + + // A category lets the command show up under the matching filter chip in the search window. + var categoryField = new EnumField("Category", m_Category); + categoryField.RegisterValueChangedCallback(evt => m_Category = (AndroidLogcatCommandCategory)evt.newValue); + r.Q("CategoryContainer").Add(categoryField); + + var saveButton = r.Q