Repository: FlaxEngine/FlaxEngine
Pull request #3131: Add android emulation options to game cooker android tab.
Author: login: Tryibion
Labels: name: enhancement
description: New feature or request
name: platform
name: editor
Created: 2025-01-02T05:11:01Z | Merged: 2025-03-04T21:14:24Z

Adds an easier way to emulate a built android application. The UI is rough right now, but it does the job.
This will find AVDs on the system, let the user select one and start it. Can find started AVDs and connected physical devices and can auto install it on the selected device. It will even try to start the activity automatically if it can. 

Editor options (in the android section of the Game Cooker):
![image](https://github.com/user-attachments/assets/47f61629-db48-44b1-a1bf-b93d22d129d4)

Running on emulated device:
![image](https://github.com/user-attachments/assets/b62cb2a0-e7d3-42b8-a4ee-8f0065fa2487)


This needs a little clean up before it is merged. I had to use C# process instead of Flax's Platform process because it seems that using the `SaveOutput` option crashes the editor (and this option is needed to get the different lists). Using the built in Flax Platform process it also will freeze the editor in certain cases like starting the adb log collection (even when using `WaitForEnd = false`).


A few notes from me creating this. 

- the AVDs worked better with these options (which I had to edit in the ini files):
hw.gpu.enabled=no
hw.gpu.mode=off

- I had to add ANDROID_AVD_HOME environment variable where the AVDs are stored (usually %user%\.android\avd)



Diff:
diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs
index e76cb5dfec..84dfa82dd5 100644
--- a/Source/Editor/Editor.Build.cs
+++ b/Source/Editor/Editor.Build.cs
@@ -41,6 +41,7 @@ public override void Setup(BuildOptions options)
         options.ScriptingAPI.SystemReferences.Add("System.Xml.ReaderWriter");
         options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions");
         options.ScriptingAPI.SystemReferences.Add("System.IO.Compression.ZipFile");
+        options.ScriptingAPI.SystemReferences.Add("System.Diagnostics.Process");
 
         // Enable optimizations for Editor, disable this for debugging the editor
         if (options.Configuration == TargetConfiguration.Development)
diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs
index cbe211eca3..261090d4a6 100644
--- a/Source/Editor/Windows/GameCookerWindow.cs
+++ b/Source/Editor/Windows/GameCookerWindow.cs
@@ -2,16 +2,20 @@
 
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using FlaxEditor.Content.Settings;
 using FlaxEditor.CustomEditors;
 using FlaxEditor.GUI;
 using FlaxEditor.GUI.Tabs;
+using FlaxEditor.GUI.Tree;
 using FlaxEditor.Utilities;
 using FlaxEngine;
 using FlaxEngine.GUI;
 using FlaxEngine.Utilities;
+using Debug = FlaxEngine.Debug;
+using PlatformType = FlaxEngine.PlatformType;
 
 // ReSharper disable InconsistentNaming
 // ReSharper disable MemberCanBePrivate.Local
@@ -192,6 +196,12 @@ public virtual void OnNotAvailableLayout(LayoutElementsContainer layout)
                     var label = layout.Label(text, TextAlignment.Center);
                     label.Label.AutoHeight = true;
                 }
+                
+                /// <summary>
+                /// Used to add platform specific tools if available.
+                /// </summary>
+                /// <param name="layout">The layout to start the tools at.</param>
+                public virtual void OnCustomToolsLayout(LayoutElementsContainer layout) { }
 
                 public virtual void Build()
                 {
@@ -233,6 +243,272 @@ class XboxScarlett : Platform
             class Android : Platform
             {
                 protected override BuildPlatform BuildPlatform => BuildPlatform.AndroidARM64;
+
+                /// <inheritdoc />
+                public override void OnCustomToolsLayout(LayoutElementsContainer layout)
+                {
+                    base.OnCustomToolsLayout(layout);
+
+                    // Add emulation options to android tab.
+                    layout.Space(5);
+                    var emulatorGroup = layout.Group("Tools");
+                    var sdkPath = Environment.GetEnvironmentVariable("ANDROID_HOME");
+                    if (string.IsNullOrEmpty(sdkPath))
+                        sdkPath = Environment.GetEnvironmentVariable("ANDROID_SDK");
+                    emulatorGroup.Label($"SDK path: {sdkPath}");
+                    
+                    // AVD and starting emulator
+                    var avdGroup = emulatorGroup.Group("AVD Emulator");
+                    avdGroup.Label("Note: Create AVDs using Android Studio.");
+                    avdGroup.Panel.IsClosed = false;
+                    var refreshAVDListButton = avdGroup.Button("Refresh AVD list").Button;
+                    var avdListGroup = avdGroup.Group("AVD List");
+                    avdListGroup.Panel.IsClosed = false;
+                    var noAvdLabel = avdListGroup.Label("No AVDs detected. Click Refresh.", TextAlignment.Center).Label;
+                    var avdListTree = new Tree(false)
+                    {
+                        Parent = avdListGroup.Panel,
+                    };
+                    refreshAVDListButton.Clicked += () =>
+                    {
+                        if (avdListTree.Children.Count > 0)
+                            avdListTree.DisposeChildren();
+                        
+                        var processStartInfo = new System.Diagnostics.ProcessStartInfo
+                        {
+                            FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"),
+                            Arguments = "-list-avds",
+                            RedirectStandardOutput = true,
+                            CreateNoWindow = true,
+                        };
+
+                        var process = new System.Diagnostics.Process
+                        {
+                            StartInfo = processStartInfo
+                        };
+                        process.Start();
+                        var output = new string(process.StandardOutput.ReadToEnd());
+                        /*
+                        CreateProcessSettings processSettings = new CreateProcessSettings
+                        {
+                            FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"),
+                            Arguments = "-list-avds",
+                            HiddenWindow = false,
+                            SaveOutput = true,
+                            WaitForEnd = true,
+                        };
+                        //processSettings.ShellExecute = true;
+                        FlaxEngine.Platform.CreateProcess(ref processSettings);
+                        
+                        var output = new string(processSettings.Output);*/
+                        if (output.Length == 0)
+                        {
+                            noAvdLabel.Visible = true;
+                            FlaxEditor.Editor.LogWarning("No AVDs detected.");
+                            return;
+                        }
+                        noAvdLabel.Visible = false;
+                        var splitOutput = output.Split('\n');
+                        foreach (var line in splitOutput)
+                        {
+                            if (string.IsNullOrEmpty(line.Trim()))
+                                continue;
+                            var item = new TreeNode
+                            {
+                                Text = line.Trim(),
+                                Parent = avdListTree,
+                            };
+                        }
+                        avdListGroup.Panel.IsClosed = false;
+                    };
+
+                    avdGroup.Label("Emulator AVD Commands:");
+                    var commandsTextBox = avdGroup.TextBox().TextBox;
+                    commandsTextBox.IsMultiline = false;
+                    commandsTextBox.Text = "-no-snapshot-load -no-boot-anim"; // TODO: save user changes
+
+                    var startEmulatorButton = avdGroup.Button("Start AVD Emulator").Button;
+                    startEmulatorButton.TooltipText = "Starts selected AVD from list.";
+                    startEmulatorButton.Clicked += () =>
+                    {
+                        if (avdListTree.Selection.Count == 0)
+                            return;
+
+                        CreateProcessSettings processSettings = new CreateProcessSettings
+                        {
+                            FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"),
+                            Arguments = $"-avd {avdListTree.Selection[0].Text} {commandsTextBox.Text}",
+                            HiddenWindow = true,
+                            SaveOutput = false,
+                            WaitForEnd = false,
+                        };
+                        processSettings.ShellExecute = true;
+                        FlaxEngine.Platform.CreateProcess(ref processSettings);
+                    };
+                    
+                    emulatorGroup.Space(2);
+                    
+                    // Device
+                    var installGroup = emulatorGroup.Group("Install");
+                    installGroup.Panel.IsClosed = false;
+                    installGroup.Label("Note: Used to install to AVD or physical devices.");
+                    var refreshDeviceListButton = installGroup.Button("Refresh device list").Button;
+                    var deviceListGroup = installGroup.Group("List of devices");
+                    deviceListGroup.Panel.IsClosed = false;
+                    var noDevicesLabel = deviceListGroup.Label("No devices found. Click Refresh.", TextAlignment.Center).Label;
+                    var deviceListTree = new Tree(false)
+                    {
+                        Parent = deviceListGroup.Panel,
+                    };
+                    refreshDeviceListButton.Clicked += () =>
+                    {
+                        if (deviceListTree.Children.Count > 0)
+                            deviceListTree.DisposeChildren();
+
+                        var processStartInfo = new System.Diagnostics.ProcessStartInfo
+                        {
+                            FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"),
+                            Arguments = "devices -l",
+                            RedirectStandardOutput = true,
+                            CreateNoWindow = true,
+                        };
+
+                        var process = new System.Diagnostics.Process
+                        {
+                            StartInfo = processStartInfo
+                        };
+                        process.Start();
+                        var output = new string(process.StandardOutput.ReadToEnd());
+                        /*
+                        CreateProcessSettings processSettings = new CreateProcessSettings
+                        {
+                            FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"),
+                            Arguments = "devices -l",
+                            HiddenWindow = false,
+                            //SaveOutput = true,
+                            WaitForEnd = true,
+                        };
+                        processSettings.SaveOutput = true;
+                        processSettings.ShellExecute = false;
+                        FlaxEngine.Platform.CreateProcess(ref processSettings);
+                        
+                        var output = new string(processSettings.Output);
+                        */
+                        
+                        if (output.Length > 0 && !output.Equals("List of devices attached", StringComparison.Ordinal))
+                        {
+                            noDevicesLabel.Visible = false;
+                            var splitLines = output.Split('\n');
+                            foreach (var line in splitLines)
+                            {
+                                if (line.Trim().Equals("List of devices attached", StringComparison.Ordinal) || string.IsNullOrEmpty(line.Trim()))
+                                    continue;
+                                
+                                var tab = line.Split("device ");
+                                if (tab.Length < 2)
+                                    continue;
+                                var item = new TreeNode
+                                {
+                                    Text = $"{tab[0].Trim()} - {tab[1].Trim()}",
+                                    Tag = tab[0].Trim(),
+                                    Parent = deviceListTree,
+                                };
+                            }
+                        }
+                        else
+                        {
+                            noDevicesLabel.Visible = true;
+                        }
+
+                        deviceListGroup.Panel.IsClosed = false;
+                    };
+
+                    var autoStart = installGroup.Checkbox("Try to auto start activity on device.");
+                    var installButton = installGroup.Button("Install APK to Device").Button;
+                    installButton.TooltipText = "Installs APK from the output folder to the selected device.";
+                    installButton.Clicked += () =>
+                    {
+                        if (deviceListTree.Selection.Count == 0)
+                            return;
+                        
+                        // Get built APK at output path
+                        string output = StringUtils.ConvertRelativePathToAbsolute(Globals.ProjectFolder, StringUtils.NormalizePath(Output));
+                        if (!Directory.Exists(output))
+                        {
+                            FlaxEditor.Editor.LogWarning("Can not copy APK because output folder does not exist.");
+                            return;
+                        }
+                        
+                        var apkFiles = Directory.GetFiles(output, "*.apk");
+                        if (apkFiles.Length == 0)
+                        {
+                            FlaxEditor.Editor.LogWarning("Can not copy APK because no .apk files were found in output folder.");
+                            return;
+                        }
+
+                        string apkFilesString = string.Empty;
+                        for (int i = 0; i < apkFiles.Length; i++)
+                        {
+                            var file = apkFiles[i];
+                            if (i == 0)
+                            {
+                                apkFilesString = $"\"{file}\"";
+                                continue;
+                            }
+                            apkFilesString += $" \"{file}\"";
+                        }
+                        
+                        CreateProcessSettings processSettings = new CreateProcessSettings
+                        {
+                            FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"),
+                            Arguments = $"-s {deviceListTree.Selection[0].Tag} {(apkFiles.Length > 1 ? "install-multiple" : "install")} {apkFilesString}",
+                            LogOutput = true,
+                        };
+                        FlaxEngine.Platform.CreateProcess(ref processSettings);
+
+                        if (autoStart.CheckBox.Checked)
+                        {
+                            var gameSettings = GameSettings.Load();
+                            var productName = gameSettings.ProductName.Replace(" ", "").ToLower();
+                            var companyName = gameSettings.CompanyName.Replace(" ", "").ToLower();
+                            CreateProcessSettings processSettings1 = new CreateProcessSettings
+                            {
+                                FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"),
+                                Arguments = $"shell am start -n com.{companyName}.{productName}/com.flaxengine.GameActivity",
+                                LogOutput = true,
+                            };
+                            FlaxEngine.Platform.CreateProcess(ref processSettings1);
+                        }
+                    };
+
+                    var adbLogButton = emulatorGroup.Button("Start adb log collecting").Button;
+                    adbLogButton.TooltipText = "In debug and development builds the engine and game logs can be output directly to the adb.";
+                    adbLogButton.Clicked += () =>
+                    {
+                        var processStartInfo = new System.Diagnostics.ProcessStartInfo
+                        {
+                            FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"),
+                            Arguments = "logcat Flax:I *:S",
+                            CreateNoWindow = false,
+                            WindowStyle = ProcessWindowStyle.Normal,
+                        };
+
+                        var process = new System.Diagnostics.Process
+                        {
+                            StartInfo = processStartInfo
+                        };
+                        process.Start();
+                        /*
+                        CreateProcessSettings processSettings = new CreateProcessSettings
+                        {
+                            FileName = Path.Combine(sdkPath, "platform-tools", "adb.exe"),
+                            Arguments = $"logcat Flax:I *:S",
+                            WaitForEnd = false,
+                        };
+                        FlaxEngine.Platform.CreateProcess(ref processSettings);
+                        */
+                    };
+                }
             }
 
             class Switch : Platform
@@ -343,6 +619,7 @@ public override void Initialize(LayoutElementsContainer layout)
 
                         _buildButton = layout.Button("Build").Button;
                         _buildButton.Clicked += OnBuildClicked;
+                        platformObj.OnCustomToolsLayout(layout);
                     }
                     else
                     {


Reviews:
user: login: mafiesto4
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-02T10:45:07Z
user: login: Tryibion
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-02T20:53:55Z
user: login: Tryibion
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-02T20:58:01Z
user: login: mafiesto4
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T19:42:38Z

Review comments:
diff_hunk: @@ -343,6 +347,269 @@ public override void Initialize(LayoutElementsContainer layout)
 
                         _buildButton = layout.Button("Build").Button;
                         _buildButton.Clicked += OnBuildClicked;
+                        
+                        // Add emulation options to android tab.
+                        if (_platform == PlatformType.Android)
+                        {
+                            layout.Space(5);
+                            var emulatorGroup = layout.Group("Tools");
+                            var sdkPath = Environment.GetEnvironmentVariable("ANDROID_HOME");
+                            if (string.IsNullOrEmpty(sdkPath))
+                                sdkPath = Environment.GetEnvironmentVariable("ANDROID_SDK");
+                            emulatorGroup.Label($"SDK path: {sdkPath}");
+                            
+                            // AVD and starting emulator
+                            var avdGroup = emulatorGroup.Group("AVD Emulator");
+                            avdGroup.Label("Note: Create AVDs using Android Studio.");
+                            avdGroup.Panel.IsClosed = false;
+                            var refreshAVDListButton = avdGroup.Button("Refresh AVD list").Button;
+                            var avdListGroup = avdGroup.Group("AVD List");
+                            avdListGroup.Panel.IsClosed = false;
+                            var noAvdLabel = avdListGroup.Label("No AVDs detected. Click Refresh.", TextAlignment.Center).Label;
+                            var avdListTree = new Tree(false)
+                            {
+                                Parent = avdListGroup.Panel,
+                            };
+                            refreshAVDListButton.Clicked += () =>
+                            {
+                                if (avdListTree.Children.Count > 0)
+                                    avdListTree.DisposeChildren();
+                                
+                                var processStartInfo = new System.Diagnostics.ProcessStartInfo
+                                {
+                                    FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"),
+                                    Arguments = "-list-avds",
+                                    RedirectStandardOutput = true,
+                                    CreateNoWindow = true,
+                                };
+
+                                var process = new System.Diagnostics.Process
path: Source/Editor/Windows/GameCookerWindow.cs
user: login: mafiesto4
body: Have you tried using `FlaxEngine.Platform.CreateProcess` and `FlaxEngine.CreateProcessSettings` to start process via Flax API instead of .NET one? It supports capturing output as array of chars and auto-logging process to the Output Log.
created_at: 2025-03-02T10:45:07Z
author_association: MEMBER
diff_hunk: @@ -343,6 +347,269 @@ public override void Initialize(LayoutElementsContainer layout)
 
                         _buildButton = layout.Button("Build").Button;
                         _buildButton.Clicked += OnBuildClicked;
+                        
+                        // Add emulation options to android tab.
+                        if (_platform == PlatformType.Android)
+                        {
+                            layout.Space(5);
+                            var emulatorGroup = layout.Group("Tools");
+                            var sdkPath = Environment.GetEnvironmentVariable("ANDROID_HOME");
+                            if (string.IsNullOrEmpty(sdkPath))
+                                sdkPath = Environment.GetEnvironmentVariable("ANDROID_SDK");
+                            emulatorGroup.Label($"SDK path: {sdkPath}");
+                            
+                            // AVD and starting emulator
+                            var avdGroup = emulatorGroup.Group("AVD Emulator");
+                            avdGroup.Label("Note: Create AVDs using Android Studio.");
+                            avdGroup.Panel.IsClosed = false;
+                            var refreshAVDListButton = avdGroup.Button("Refresh AVD list").Button;
+                            var avdListGroup = avdGroup.Group("AVD List");
+                            avdListGroup.Panel.IsClosed = false;
+                            var noAvdLabel = avdListGroup.Label("No AVDs detected. Click Refresh.", TextAlignment.Center).Label;
+                            var avdListTree = new Tree(false)
+                            {
+                                Parent = avdListGroup.Panel,
+                            };
+                            refreshAVDListButton.Clicked += () =>
+                            {
+                                if (avdListTree.Children.Count > 0)
+                                    avdListTree.DisposeChildren();
+                                
+                                var processStartInfo = new System.Diagnostics.ProcessStartInfo
+                                {
+                                    FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"),
+                                    Arguments = "-list-avds",
+                                    RedirectStandardOutput = true,
+                                    CreateNoWindow = true,
+                                };
+
+                                var process = new System.Diagnostics.Process
path: Source/Editor/Windows/GameCookerWindow.cs
user: login: Tryibion
body: Yes I tried this originally, but there is a crash when using the built in Flax platform to start a process when capture output is enabled and I could not figure out why it was crashing.
created_at: 2025-03-02T20:53:55Z
author_association: MEMBER
diff_hunk: @@ -343,6 +347,269 @@ public override void Initialize(LayoutElementsContainer layout)
 
                         _buildButton = layout.Button("Build").Button;
                         _buildButton.Clicked += OnBuildClicked;
+                        
+                        // Add emulation options to android tab.
+                        if (_platform == PlatformType.Android)
+                        {
+                            layout.Space(5);
+                            var emulatorGroup = layout.Group("Tools");
+                            var sdkPath = Environment.GetEnvironmentVariable("ANDROID_HOME");
+                            if (string.IsNullOrEmpty(sdkPath))
+                                sdkPath = Environment.GetEnvironmentVariable("ANDROID_SDK");
+                            emulatorGroup.Label($"SDK path: {sdkPath}");
+                            
+                            // AVD and starting emulator
+                            var avdGroup = emulatorGroup.Group("AVD Emulator");
+                            avdGroup.Label("Note: Create AVDs using Android Studio.");
+                            avdGroup.Panel.IsClosed = false;
+                            var refreshAVDListButton = avdGroup.Button("Refresh AVD list").Button;
+                            var avdListGroup = avdGroup.Group("AVD List");
+                            avdListGroup.Panel.IsClosed = false;
+                            var noAvdLabel = avdListGroup.Label("No AVDs detected. Click Refresh.", TextAlignment.Center).Label;
+                            var avdListTree = new Tree(false)
+                            {
+                                Parent = avdListGroup.Panel,
+                            };
+                            refreshAVDListButton.Clicked += () =>
+                            {
+                                if (avdListTree.Children.Count > 0)
+                                    avdListTree.DisposeChildren();
+                                
+                                var processStartInfo = new System.Diagnostics.ProcessStartInfo
+                                {
+                                    FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"),
+                                    Arguments = "-list-avds",
+                                    RedirectStandardOutput = true,
+                                    CreateNoWindow = true,
+                                };
+
+                                var process = new System.Diagnostics.Process
path: Source/Editor/Windows/GameCookerWindow.cs
user: login: Tryibion
body: There was also an issue using the built in platform process where the editor just freezes when starting an emulator.
created_at: 2025-03-02T20:58:01Z
author_association: MEMBER
diff_hunk: @@ -343,6 +347,269 @@ public override void Initialize(LayoutElementsContainer layout)
 
                         _buildButton = layout.Button("Build").Button;
                         _buildButton.Clicked += OnBuildClicked;
+                        
+                        // Add emulation options to android tab.
+                        if (_platform == PlatformType.Android)
+                        {
+                            layout.Space(5);
+                            var emulatorGroup = layout.Group("Tools");
+                            var sdkPath = Environment.GetEnvironmentVariable("ANDROID_HOME");
+                            if (string.IsNullOrEmpty(sdkPath))
+                                sdkPath = Environment.GetEnvironmentVariable("ANDROID_SDK");
+                            emulatorGroup.Label($"SDK path: {sdkPath}");
+                            
+                            // AVD and starting emulator
+                            var avdGroup = emulatorGroup.Group("AVD Emulator");
+                            avdGroup.Label("Note: Create AVDs using Android Studio.");
+                            avdGroup.Panel.IsClosed = false;
+                            var refreshAVDListButton = avdGroup.Button("Refresh AVD list").Button;
+                            var avdListGroup = avdGroup.Group("AVD List");
+                            avdListGroup.Panel.IsClosed = false;
+                            var noAvdLabel = avdListGroup.Label("No AVDs detected. Click Refresh.", TextAlignment.Center).Label;
+                            var avdListTree = new Tree(false)
+                            {
+                                Parent = avdListGroup.Panel,
+                            };
+                            refreshAVDListButton.Clicked += () =>
+                            {
+                                if (avdListTree.Children.Count > 0)
+                                    avdListTree.DisposeChildren();
+                                
+                                var processStartInfo = new System.Diagnostics.ProcessStartInfo
+                                {
+                                    FileName = Path.Combine(sdkPath, "emulator", "emulator.exe"),
+                                    Arguments = "-list-avds",
+                                    RedirectStandardOutput = true,
+                                    CreateNoWindow = true,
+                                };
+
+                                var process = new System.Diagnostics.Process
path: Source/Editor/Windows/GameCookerWindow.cs
user: login: mafiesto4
body: Okay fine. Let's use it for now.
created_at: 2025-03-03T19:42:38Z
author_association: MEMBER

PR comments:
user: login: Tryibion
created_at: 2025-03-02T20:54:48Z
body: This could also just be migrated into an `AndroidTools` project plugin if it would better fit there.
author_association: MEMBER
user: login: mafiesto4
created_at: 2025-03-03T19:45:07Z
body: @Tryibion Could you move that Android-specific code into some `virtual` function inside `GameCookerWindow.Platform` like `virtual void OnCustomTools(layout)` and override it specific for Android? In future, we might add similar utilities for other platforms. At some point maybe shared Device Manager or similar but for now that's good.
author_association: MEMBER