diff --git a/com.unity.mobile.android-logcat/Editor/AndroidLogcatConsoleWindow.cs b/com.unity.mobile.android-logcat/Editor/AndroidLogcatConsoleWindow.cs index 2065173..c627d5d 100644 --- a/com.unity.mobile.android-logcat/Editor/AndroidLogcatConsoleWindow.cs +++ b/com.unity.mobile.android-logcat/Editor/AndroidLogcatConsoleWindow.cs @@ -340,6 +340,9 @@ private void MenuToolsSelection(object userData, string[] options, int selected) case ToolsContextMenu.StacktraceUtility: AndroidLogcatStacktraceWindow.ShowStacktraceWindow(); break; + case ToolsContextMenu.PackageInformation: + AndroidLogcatPackagesWindow.ShowWindow(); + break; case ToolsContextMenu.WindowMemory: m_Runtime.UserSettings.ExtraWindowState.Type = ExtraWindow.Memory; break; @@ -363,6 +366,7 @@ private void DoToolsGUI() contextMenu.Add(ToolsContextMenu.ScreenCapture, "Screen Capture"); contextMenu.Add(ToolsContextMenu.OpenTerminal, "Open Terminal"); contextMenu.Add(ToolsContextMenu.StacktraceUtility, "Stacktrace Utility"); + contextMenu.Add(ToolsContextMenu.PackageInformation, "Package Information"); 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 fc951bf..703772a 100644 --- a/com.unity.mobile.android-logcat/Editor/AndroidLogcatContextMenu.cs +++ b/com.unity.mobile.android-logcat/Editor/AndroidLogcatContextMenu.cs @@ -22,6 +22,7 @@ internal enum ToolsContextMenu ScreenCapture, OpenTerminal, StacktraceUtility, + PackageInformation, WindowMemory, WindowInputs, WindowHidden diff --git a/com.unity.mobile.android-logcat/Editor/AndroidLogcatPackageEntry.cs b/com.unity.mobile.android-logcat/Editor/AndroidLogcatPackageEntry.cs new file mode 100644 index 0000000..522c151 --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/AndroidLogcatPackageEntry.cs @@ -0,0 +1,55 @@ +using System; + +namespace Unity.Android.Logcat +{ + [Serializable] + internal class PackageEntry + { + public string Name { set; get; } + public string Installer { set; get; } + public string UID { set; get; } + + internal int GetId() => GetHashCode(); + + internal void Launch() + { + var adb = AndroidLogcatManager.instance.Runtime.Tools.ADB; + var device = AndroidLogcatManager.instance.Runtime.DeviceQuery.SelectedDevice; + // Not using 'shell am start' since it requires knowing the activity name + var cmd = $"-s {device.Id} shell monkey -p {Name} -c android.intent.category.LAUNCHER 1"; + var output = adb.Run(new[] { cmd }, "Unable to launch package"); + UnityEngine.Debug.Log(output); + } + internal void Pause() + { + var adb = AndroidLogcatManager.instance.Runtime.Tools.ADB; + var device = AndroidLogcatManager.instance.Runtime.DeviceQuery.SelectedDevice; + var cmd = $"-s {device.Id} shell input keyevent 3"; + var output = adb.Run(new[] { cmd }, "Unable to pause the package"); + UnityEngine.Debug.Log(output); + } + + internal void Stop() + { + var adb = AndroidLogcatManager.instance.Runtime.Tools.ADB; + var device = AndroidLogcatManager.instance.Runtime.DeviceQuery.SelectedDevice; + var cmd = $"-s {device.Id} shell am force-stop {Name}"; + var output = adb.Run(new[] { cmd }, "Unable to stop package"); + UnityEngine.Debug.Log(output); + } + + internal void Uninstall() + { + var adb = AndroidLogcatManager.instance.Runtime.Tools.ADB; + var device = AndroidLogcatManager.instance.Runtime.DeviceQuery.SelectedDevice; + var cmd = $"-s {device.Id} uninstall {Name}"; + var output = adb.Run(new[] { cmd }, "Unable to uninstall the package"); + UnityEngine.Debug.Log(output); + } + + public override string ToString() + { + return $"{Name} Installer={Installer} UID={UID}"; + } + } +} diff --git a/com.unity.mobile.android-logcat/Editor/AndroidLogcatPackageEntry.cs.meta b/com.unity.mobile.android-logcat/Editor/AndroidLogcatPackageEntry.cs.meta new file mode 100644 index 0000000..9a9f675 --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/AndroidLogcatPackageEntry.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ee72d8225fceb2346b59c264f5d61964 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.mobile.android-logcat/Editor/AndroidLogcatPackagesWindow.cs b/com.unity.mobile.android-logcat/Editor/AndroidLogcatPackagesWindow.cs new file mode 100644 index 0000000..68dc992 --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/AndroidLogcatPackagesWindow.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Unity.Android.Logcat +{ + // adb shell dumpsys package + + // adb shell cmd package list packages + // list packages[-f] [-d] + // [-e] + // [-s] + // [-3] + // [-i] + // [-l] + // [-u] + // [-U] + // [--uid UID] + // [--user USER_ID] + // [FILTER] + // Prints all packages; optionally only those whose name contains the text in FILTER. + // Options: + // -f: see their associated file + // -d: filter to only show disabled packages + // -e: filter to only show enabled packages + // -s: filter to only show system packages + // -3: filter to only show third party packages + // -i: see the installer for the packages + // -l: ignored(used for compatibility with older releases) + // -U: also show the package UID + // -u: also include uninstalled packages + // --uid UID: filter to only show packages with the given UID + // --user USER_ID: only list packages belonging to the given user + + // Example., adb shell cmd package list packages -3 -U -i + + internal class AndroidLogcatPackagesWindow : EditorWindow + { + private GUIContent kRefresh = new GUIContent(L10n.Tr("Refresh"), L10n.Tr("Refresh package list.")); + + AndroidLogcatRuntimeBase m_Runtime; + AndroidLogcatPackagesView m_Packages; + AndroidLogcatPackagePropertiesView m_PackageProperties; + AndroidLogcatPackageUtilities m_PackageUtilities; + AndroidLogcatDeviceSelection m_DeviceSelection; + TwoPaneSplitView m_HorizontalSplit; + TwoPaneSplitView m_VerticalSplit; + + internal static void ShowWindow() + { + // Get existing open window or if none, make a new one: + AndroidLogcatPackagesWindow window = (AndroidLogcatPackagesWindow)EditorWindow.GetWindow(typeof(AndroidLogcatPackagesWindow)); + window.titleContent = new UnityEngine.GUIContent("Package Information"); + window.Show(); + } + + private void OnEnable() + { + if (!AndroidBridge.AndroidExtensionsInstalled) + return; + + if (rootVisualElement == null) + throw new NullReferenceException("rooVisualElement is null"); + rootVisualElement.Insert(0, new IMGUIContainer(DoDebuggingGUI)); + rootVisualElement.Insert(0, new IMGUIContainer(DoToolbarGUI)); + + m_Runtime = AndroidLogcatManager.instance.Runtime; + m_Runtime.Closing += OnDisable; + m_DeviceSelection = new AndroidLogcatDeviceSelection(m_Runtime, OnDeviceSelected); + + // TODO: Add query for uxml + clone + var tree = AssetDatabase.LoadAssetAtPath("Packages/com.unity.mobile.android-logcat/Editor/UI/Layouts/AndroidLogcatPackagesLayout.uxml"); + tree.CloneTree(rootVisualElement); + + m_HorizontalSplit = rootVisualElement.Q("HorizontalSplit"); + m_HorizontalSplit.RegisterCallback(InitializeHorizontalLayout); + + m_VerticalSplit = rootVisualElement.Q("VerticalSplit"); + m_VerticalSplit.RegisterCallback(InitializeVerticalLayout); + + m_Packages = new AndroidLogcatPackagesView(m_Runtime, rootVisualElement, GetPackageEntries(m_DeviceSelection.SelectedDevice).ToList()); + m_Packages.OnPackageSelected = PackageSelected; + m_Packages.OnPackageUninstalled = PackageUninstalled; + m_PackageProperties = new AndroidLogcatPackagePropertiesView(rootVisualElement); + m_PackageUtilities = new AndroidLogcatPackageUtilities(rootVisualElement); + + m_Runtime.DeviceQuery.UpdateConnectedDevicesList(true); + } + + private void OnDisable() + { + if (!AndroidBridge.AndroidExtensionsInstalled) + return; + + if (m_Runtime == null) + return; + if (m_DeviceSelection != null) + { + m_DeviceSelection.Dispose(); + m_DeviceSelection = null; + } + m_Runtime = null; + } + + private void OnDeviceSelected(IAndroidLogcatDevice selectedDevice) + { + if (m_Packages == null) + throw new Exception("Package view was not created ?"); + m_Packages.RefreshEntries(selectedDevice, GetPackageEntries(selectedDevice).ToList()); + + PackageSelected(m_Packages.SelectedPackage); + } + + private void RefreshPackages() + { + OnDeviceSelected(m_DeviceSelection.SelectedDevice); + } + + PackageEntry[] GetPackageEntries(IAndroidLogcatDevice selectedDevice) + { + if (selectedDevice == null) + return Array.Empty(); + + return AndroidLogcatUtilities.RetrievePackages( + m_Runtime.Tools.ADB, + selectedDevice); + } + + private void InitializeHorizontalLayout(GeometryChangedEvent e) + { + m_HorizontalSplit.fixedPaneInitialDimension = m_HorizontalSplit.layout.width / 2; + m_HorizontalSplit.UnregisterCallback(InitializeHorizontalLayout); + } + + private void InitializeVerticalLayout(GeometryChangedEvent e) + { + m_VerticalSplit.fixedPaneInitialDimension = m_VerticalSplit.layout.height * 0.8f; + m_VerticalSplit.UnregisterCallback(InitializeVerticalLayout); + } + + void PackageSelected(PackageEntry entry) + { + var parser = new AndroidLogcatPackageInfoParser(AndroidLogcatUtilities.RetrievePackageProperties( + m_Runtime.Tools.ADB, m_DeviceSelection.SelectedDevice, entry)); + var entries = parser.ParsePackageInformationAsSingleEntries(entry.Name); + m_PackageProperties.RefreshProperties(entries); + + var activities = parser.ParseLaunchableActivities(entry.Name); + m_PackageUtilities.RefreshActivities(m_DeviceSelection.SelectedDevice, entry, activities); + } + + void PackageUninstalled(PackageEntry entry) + { + RefreshPackages(); + } + + void DoToolbarGUI() + { + EditorGUILayout.BeginHorizontal(AndroidLogcatStyles.toolbar); + EditorGUI.BeginDisabledGroup(true); + GUILayout.Label(GUIContent.none, AndroidLogcatStyles.StatusIcon, GUILayout.Width(30)); + EditorGUI.EndDisabledGroup(); + m_DeviceSelection.DoGUI(); + if (GUILayout.Button(kRefresh, AndroidLogcatStyles.toolbarButton)) + RefreshPackages(); + EditorGUILayout.EndHorizontal(); + } + + void DoDebuggingGUI() + { + GUILayout.Label("Developer Mode is on, showing debugging buttons:", EditorStyles.boldLabel); + EditorGUILayout.BeginHorizontal(AndroidLogcatStyles.toolbar); + + if (GUILayout.Button("Reload Me", AndroidLogcatStyles.toolbarButton)) + { + EditorUtility.RequestScriptReload(); + } + EditorGUILayout.EndHorizontal(); + } + } +} diff --git a/com.unity.mobile.android-logcat/Editor/AndroidLogcatPackagesWindow.cs.meta b/com.unity.mobile.android-logcat/Editor/AndroidLogcatPackagesWindow.cs.meta new file mode 100644 index 0000000..e4fcdaa --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/AndroidLogcatPackagesWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0e599f21ead36c74ba0a79a6851f350b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.mobile.android-logcat/Editor/AndroidLogcatUserSettings.cs b/com.unity.mobile.android-logcat/Editor/AndroidLogcatUserSettings.cs index 07d7a8e..6b7f682 100644 --- a/com.unity.mobile.android-logcat/Editor/AndroidLogcatUserSettings.cs +++ b/com.unity.mobile.android-logcat/Editor/AndroidLogcatUserSettings.cs @@ -10,6 +10,15 @@ namespace Unity.Android.Logcat [Serializable] internal class AndroidLogcatUserSettings { + [Serializable] + internal class PackagePropertiesSettings + { + [SerializeField] + internal string PacakgeFilter; + [SerializeField] + internal string PropertyFilter; + } + [Serializable] internal class InputSettings { @@ -69,6 +78,8 @@ internal class VideoSettings private VideoSettings m_CaptureVideoSettings; [SerializeField] private InputSettings m_InputSettings; + [SerializeField] + private PackagePropertiesSettings m_PackagePropertiesSettings; [SerializeField] private AutoScroll m_AutoScroll; @@ -129,7 +140,7 @@ public Priority SelectedPriority public VideoSettings CaptureVideoSettings { set => m_CaptureVideoSettings = value; get => m_CaptureVideoSettings; } public InputSettings DeviceInputSettings { set => m_InputSettings = value; get => m_InputSettings; } - + public PackagePropertiesSettings PackageWindowSettings { set => m_PackagePropertiesSettings = value; get => m_PackagePropertiesSettings; } public AutoScroll AutoScroll { set => m_AutoScroll = value; get => m_AutoScroll; } private void RefreshProcessesForSerialization() diff --git a/com.unity.mobile.android-logcat/Editor/AndroidLogcatUtilities.cs b/com.unity.mobile.android-logcat/Editor/AndroidLogcatUtilities.cs index 007f33f..20cadb1 100644 --- a/com.unity.mobile.android-logcat/Editor/AndroidLogcatUtilities.cs +++ b/com.unity.mobile.android-logcat/Editor/AndroidLogcatUtilities.cs @@ -3,6 +3,7 @@ using System.Text.RegularExpressions; using UnityEngine; using System.Collections.Generic; +using UnityEditor; using System.Linq; namespace Unity.Android.Logcat @@ -200,6 +201,80 @@ public static string GetProcessNameFromPid(AndroidBridge.ADB adb, IAndroidLogcat } } + public static PackageEntry[] RetrievePackages(AndroidBridge.ADB adb, IAndroidLogcatDevice device) + { + if (device == null) + return Array.Empty(); + + try + { + var cmd = $"-s {device.Id} shell cmd package list packages -3 -U -i"; + AndroidLogcatInternalLog.Log("{0} {1}", adb.GetADBPath(), cmd); + var output = adb.Run(new[] { cmd }, "Unable to retrieve packages"); + + if (string.IsNullOrEmpty(output)) + return Array.Empty(); + + var entries = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + var packages = new List(); + var regex = new Regex(@"package:(?\S+)\s+installer=(?\S+)\s+uid:(?\S+)"); + foreach (var e in entries) + { + var result = regex.Match(e); + if (result.Success) + { + packages.Add(new PackageEntry() + { + Name = result.Groups["name"].Value, + Installer = result.Groups["installer"].Value, + UID = result.Groups["uid"].Value + }); + } + else + { + AndroidLogcatInternalLog.Log($"Failed to parse package info:\n{e}"); + } + } + + return packages.ToArray(); + } + catch (Exception ex) + { + AndroidLogcatInternalLog.Log(ex.Message); + return Array.Empty(); + } + } + + public static string RetrievePackageProperties(AndroidBridge.ADB adb, IAndroidLogcatDevice device, PackageEntry entry) + { + if (device == null) + return string.Empty; + + try + { + var cmd = $"-s {device.Id} shell dumpsys package {entry.Name}"; + AndroidLogcatInternalLog.Log("{0} {1}", adb.GetADBPath(), cmd); + var output = adb.Run(new[] { cmd }, "Unable to retrieve package properties"); + + return output; + } + catch (Exception ex) + { + AndroidLogcatInternalLog.Log(ex.Message); + return string.Empty; + } + } + + public static bool UninstallPackageWithConfirmation(IAndroidLogcatDevice device, PackageEntry packageEntry) + { + if (!EditorUtility.DisplayDialog("Uninstall package?", $"Do you really want to uninstall '{packageEntry.Name}' ?", "Yes", "No")) + return false; + + device.UninstallPackage(packageEntry.Name); + Debug.Log($"Uninstalled '{packageEntry.Name}' on device '{device.DisplayName}'."); + return true; + } + /// /// Return the detail info of the given device. /// diff --git a/com.unity.mobile.android-logcat/Editor/Parsers/AndroidLogcatPacakgeInfoParser.cs b/com.unity.mobile.android-logcat/Editor/Parsers/AndroidLogcatPacakgeInfoParser.cs new file mode 100644 index 0000000..7980cc1 --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/Parsers/AndroidLogcatPacakgeInfoParser.cs @@ -0,0 +1,204 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using System.Collections.Generic; + +namespace Unity.Android.Logcat +{ + internal class AndroidLogcatPackageInfoParser + { + internal static readonly string FailedKey = "Failed"; + private string m_Contents; + + internal AndroidLogcatPackageInfoParser(string contents) + { + m_Contents = contents.Replace("\r\n", "\n"); + } + + public List ParsePackageInformationAsSingleEntries(string packageName) + { + var lines = GetPackageBlock(packageName); + if (lines == null || lines.Length <= 1) + return new List(); + var strip = lines[1].TakeWhile(c => char.IsWhiteSpace(c)).Count(); + + var entries = lines.Select(c => c.Substring(strip)).ToList(); + entries.RemoveAt(0); + return entries; + } + + public List ParseLaunchableActivities(string packageName) + { + var lines = GetActivitiesBlock(); + var activityResolveRegex = new Regex(Regex.Escape($"{packageName}/") + "(?\\S+) filter"); + if (lines == null || lines.Length <= 1) + return new List(); + + var activities = new List(); + foreach (var line in lines) + { + var result = activityResolveRegex.Match(line); + if (!result.Success) + continue; + activities.Add(result.Groups["activityName"].Value); + } + return activities; + } + + /// + /// Parses information as [key]=[value] + /// In some cases like permissions or User, it's difficult to present such data + /// For ex., + /// requested permissions: + /// android.permission.INTERNET + /// android.permission.ACCESS_NETWORK_STATE + /// User 0: ceDataInode=311793 installed=true hidden=false suspended=false distractionFlags=0 stopped=false notLaunched=false enabled=0 instant=false virtual=false + /// gids=[3003] + /// runtime permissions: + /// + public List> ParsePackageInformationAsPairs(string packageName) + { + var lines = GetPackageBlock(packageName); + if (lines == null) + return new List>(); + + return ParsePackageInformationAsPairs(lines, packageName); + } + + private List> ParsePackageInformationAsPairs(string[] lines, string packageName) + { + var entries = new List>(); + if (lines.Length == 0) + return entries; + + var regexPackageName = Regex.Escape(packageName); + var title = new Regex($"Package.*{regexPackageName}.*\\:"); + + if (!title.Match(lines[0]).Success) + throw new Exception($"Expected 'Package [{packageName}] () :', but got '{lines[0]}'"); + + var keyValueRegex = new Regex(@"\s+(?\w+)\=(?.*)"); + for (var i = 1; i < lines.Length;) + { + var l = lines[i]; + + // Is it a permissions block? + if (l.EndsWith("permissions:")) + { + var key = l.Trim(); + var values = ParsePermissionBlock(lines, i, out var blockLength); + entries.Add(new KeyValuePair(key, values)); + i += blockLength; + continue; + } + + // Normal entry + var result = keyValueRegex.Match(l); + + if (result.Success) + { + var key = result.Groups["key"].Value; + var value = result.Groups["value"] == null ? string.Empty : result.Groups["value"].Value; + + entries.Add(new KeyValuePair(key, value)); + } + else + { + // Failed to parse value, add it as well for easier debugging + entries.Add(new KeyValuePair(FailedKey, l)); + } + + i++; + } + return entries; + } + + private int GetBlockEnd(string[] lines, int blockStart) + { + // Figure out block end + // For ex., + // requested permissions: + // android.permission.INTERNET + // android.permission.ACCESS_NETWORK_STATE + // if blockStart == 0, blockEnd will be 3 + var l = lines[blockStart]; + var indentation = l.TakeWhile(c => char.IsWhiteSpace(c)).Count(); + + var blockEnd = blockStart + 1; + while (blockEnd < lines.Length) + { + l = lines[blockEnd]; + var blockIndentation = l.TakeWhile(c => char.IsWhiteSpace(c)).Count(); + if (blockIndentation > indentation) + blockEnd++; + else + break; + } + + return blockEnd - 1; + } + + private string ParsePermissionBlock(string[] lines, int permissionsStart, out int blockLength) + { + blockLength = 1; + var value = string.Empty; + + if (permissionsStart + 1 >= lines.Length) + return string.Empty; + + var permissionsEnd = GetBlockEnd(lines, permissionsStart); + + // Collect values + for (int p = permissionsStart + 1; p <= permissionsEnd; p++) + { + if (value.Length > 0) + value += ", "; + value += lines[p].Trim(); + } + blockLength = permissionsEnd - permissionsStart + 1; + return value; + } + + private string[] GetActivitiesBlock() + { + return GetBlock($"Activity Resolver Table:"); + } + + private string[] GetPackageBlock(string packageName) + { + return GetBlock($"Package [{packageName}]"); + } + + private string[] GetBlock(string blockStartName) + { + var lines = m_Contents.Split(new[] { '\n' }).ToArray(); + for (int i = 0; i < lines.Length; i++) + { + // Find block start + if (!lines[i].Trim().StartsWith(blockStartName)) + continue; + + var blockStart = i; + var blockEnd = -1; + for (var l = blockStart + 1; l < lines.Length; l++) + { + blockEnd = l; + // Find block end + if (string.IsNullOrEmpty(lines[blockEnd].Trim())) + { + blockEnd--; + break; + } + } + + var length = blockEnd - blockStart + 1; + if (length <= 0) + return Array.Empty(); + + return lines.Skip(i).Take(length).ToArray(); + } + + return Array.Empty(); + } + } +} diff --git a/com.unity.mobile.android-logcat/Editor/Parsers/AndroidLogcatPacakgeInfoParser.cs.meta b/com.unity.mobile.android-logcat/Editor/Parsers/AndroidLogcatPacakgeInfoParser.cs.meta new file mode 100644 index 0000000..c1ed77b --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/Parsers/AndroidLogcatPacakgeInfoParser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 146dcd6dccd16e9488544d6f58f9b1a9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.mobile.android-logcat/Editor/UI/Layouts.meta b/com.unity.mobile.android-logcat/Editor/UI/Layouts.meta new file mode 100644 index 0000000..4746063 --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/UI/Layouts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2c3fe81b3bb81c04aac4dbc4cad17f37 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.mobile.android-logcat/Editor/UI/Layouts/AndroidLogcatPackagesLayout.uxml b/com.unity.mobile.android-logcat/Editor/UI/Layouts/AndroidLogcatPackagesLayout.uxml new file mode 100644 index 0000000..74e0ca4 --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/UI/Layouts/AndroidLogcatPackagesLayout.uxml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/com.unity.mobile.android-logcat/Editor/UI/Layouts/AndroidLogcatPackagesLayout.uxml.meta b/com.unity.mobile.android-logcat/Editor/UI/Layouts/AndroidLogcatPackagesLayout.uxml.meta new file mode 100644 index 0000000..e8237fc --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/UI/Layouts/AndroidLogcatPackagesLayout.uxml.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 6a0b5f795cb97794cb65533a587e917f +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0} diff --git a/com.unity.mobile.android-logcat/Editor/UI/Packages.meta b/com.unity.mobile.android-logcat/Editor/UI/Packages.meta new file mode 100644 index 0000000..6de9c1b --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/UI/Packages.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 598088db5abdbee4ab7e54a093b55c5c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.mobile.android-logcat/Editor/UI/Packages/AndroidLogcatPackageLabel.cs b/com.unity.mobile.android-logcat/Editor/UI/Packages/AndroidLogcatPackageLabel.cs new file mode 100644 index 0000000..a8bb87f --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/UI/Packages/AndroidLogcatPackageLabel.cs @@ -0,0 +1,29 @@ +using UnityEngine.UIElements; + +namespace Unity.Android.Logcat +{ + interface PackageEntryVisualElement + { + PackageEntry Entry { set; get; } + int Index { set; get; } + } + + class PackageEntryLabel : Label, PackageEntryVisualElement + { + public PackageEntry Entry { set; get; } + public int Index { set; get; } + } + + class PackageEntryButton : Button, PackageEntryVisualElement + { + public PackageEntry Entry { set; get; } + + public int Index { set; get; } + } + + class PackagePropertyLabel : Label + { + public int Index { set; get; } + } + +} diff --git a/com.unity.mobile.android-logcat/Editor/UI/Packages/AndroidLogcatPackageLabel.cs.meta b/com.unity.mobile.android-logcat/Editor/UI/Packages/AndroidLogcatPackageLabel.cs.meta new file mode 100644 index 0000000..6b395dd --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/UI/Packages/AndroidLogcatPackageLabel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 41599afae27dd384b9ba08befb533c17 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.mobile.android-logcat/Editor/UI/Packages/AndroidLogcatPackagePropertiesView.cs b/com.unity.mobile.android-logcat/Editor/UI/Packages/AndroidLogcatPackagePropertiesView.cs new file mode 100644 index 0000000..ec62dc6 --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/UI/Packages/AndroidLogcatPackagePropertiesView.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine.UIElements; +using UnityEditor; +using System.Text; +using UnityEditor.UIElements; + +namespace Unity.Android.Logcat +{ + internal class AndroidLogcatPackagePropertiesView + { + enum PackagePropertiesContextMenu + { + CopyProperty, + CopyAll + } + + MultiColumnListView m_ListView; + TextField m_Filter; + + List m_UnfilteredEntries; + List m_FilteredEntries; + + internal AndroidLogcatPackagePropertiesView(VisualElement root, List properties = null) + { + m_UnfilteredEntries = properties; + if (m_UnfilteredEntries == null) + m_UnfilteredEntries = new List(); + m_FilteredEntries = new List(); + + m_ListView = root.Q("package_properties"); + m_Filter = root.Q("package_properties_filter"); + + // TODO: take filter from settings + m_Filter.RegisterValueChangedCallback((s) => + { + FilterBy(s.newValue); + }); + + m_ListView.itemsSource = m_UnfilteredEntries; + CreateLabel("value"); + // CreateLabel("value", (e) => e.Value, (e) => $"{e.Key} = {e.Value}"); + } + + internal void RefreshProperties(List packageEntries) + { + m_UnfilteredEntries = packageEntries; + FilterBy(m_Filter.value); + } + + private void FilterBy(string filter) + { + m_FilteredEntries.Clear(); + if (string.IsNullOrEmpty(filter)) + { + m_FilteredEntries.AddRange(m_UnfilteredEntries); + } + else + { + foreach (var e in m_UnfilteredEntries) + { + if (e.Contains(filter)) + m_FilteredEntries.Add(e); + } + } + + m_ListView.itemsSource = m_FilteredEntries; + m_ListView.RefreshItems(); + } + + void CreateLabel(string name) + { + var id = name.ToLower(); + m_ListView.columns[id].makeCell = () => + { + var label = new PackagePropertyLabel(); + label.style.marginLeft = 5.0f; + + label.RegisterCallback((e, l) => + { + if (e.button == 1) + m_ListView.SetSelectionWithoutNotify(new[] { l.Index }); + + }, label); + + label.RegisterCallback((e, l) => + { + if (e.button != 1) + return; + + m_ListView.SetSelection(l.Index); + + var contextMenu = new AndroidContextMenu(); + contextMenu.Add(PackagePropertiesContextMenu.CopyProperty, "Copy Property"); + contextMenu.Add(PackagePropertiesContextMenu.CopyAll, "Copy All"); + + contextMenu.Show(e.mousePosition, (userData, options, selected) => + { + var sender = (AndroidContextMenu)userData; + var item = sender.GetItemAt(selected); + if (item == null) + return; + + switch (item.Item) + { + case PackagePropertiesContextMenu.CopyProperty: + EditorGUIUtility.systemCopyBuffer = l.text; + break; + case PackagePropertiesContextMenu.CopyAll: + var data = new StringBuilder(); + foreach (var p in m_FilteredEntries) + { + data.AppendLine(p.ToString()); + } + EditorGUIUtility.systemCopyBuffer = data.ToString(); + break; + } + }); + + }, label); + + return label; + }; + m_ListView.columns[id].bindCell = (element, index) => + { + var label = (PackagePropertyLabel)element; + var entry = (string)m_ListView.itemsSource[index]; + label.text = entry; + label.tooltip = entry.Trim(); + label.Index = index; + }; + } + } +} diff --git a/com.unity.mobile.android-logcat/Editor/UI/Packages/AndroidLogcatPackagePropertiesView.cs.meta b/com.unity.mobile.android-logcat/Editor/UI/Packages/AndroidLogcatPackagePropertiesView.cs.meta new file mode 100644 index 0000000..1bb42e0 --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/UI/Packages/AndroidLogcatPackagePropertiesView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b75bf29df6402247b5ee22cd24c0224 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.mobile.android-logcat/Editor/UI/Packages/AndroidLogcatPackageUtilities.cs b/com.unity.mobile.android-logcat/Editor/UI/Packages/AndroidLogcatPackageUtilities.cs new file mode 100644 index 0000000..1a5737e --- /dev/null +++ b/com.unity.mobile.android-logcat/Editor/UI/Packages/AndroidLogcatPackageUtilities.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine.UIElements; +using UnityEditor.UIElements; + + +namespace Unity.Android.Logcat +{ + internal class AndroidLogcatPackageUtilities + { + enum PackageUtilitiesTab + { + LaunchOptions, + Others + } + + ToolbarToggle m_LaunchOptions; + ToolbarToggle m_Others; + VisualElement m_TabContents; + VisualElement m_TabLaunchOptions; + VisualElement m_TabOthers; + ListView m_LaunchableActivities; + + Button m_LaunchApplication; + Button m_StopApplication; + + IAndroidLogcatDevice m_Device; + PackageEntry m_PackageEntry; + + internal string SelectedActivity + { + get + { + var item = m_LaunchableActivities.selectedItem; + return item?.ToString(); + } + } + + internal AndroidLogcatPackageUtilities(VisualElement root) + { + m_TabContents = root.Q("package-tabs-contents"); + m_LaunchOptions = root.Q("package-launch-options"); + m_Others = root.Q("package-others"); + + m_LaunchOptions.RegisterValueChangedCallback((v) => SelectTab(PackageUtilitiesTab.LaunchOptions)); + m_Others.RegisterValueChangedCallback((v) => SelectTab(PackageUtilitiesTab.Others)); + + m_TabLaunchOptions = root.Q("package-tab-launch-options"); + m_TabOthers = root.Q("package-tab-others"); + m_LaunchableActivities = root.Q("launchable-activities"); + + m_LaunchApplication = root.Q