Implement Flex Mode on an Unreal Engine Game


Learn how to implement Flex mode on an Unreal Engine game using Android Jetpack WindowManager and raw Java Native Interface (JNI).


The flexible hinge and glass display on Galaxy foldable devices, such as the Galaxy Z Fold2, Z Fold3, Z Flip, and Z Flip3, let the phone remains propped open while you use apps. When the phone is partially folded, it will go into Flex mode.

Apps will reorient to fit the screen, letting you watch videos or play games without holding the phone. For example, you can set the device on a flat surface, like on a table, and use the bottom half of the screen to navigate. Unfold the phone to use the apps in full screen mode, and partially fold it again to return to Flex mode. To provide users with a convenient and versatile foldable experience, developers need to optimize their apps to meet the Flex mode standard.

Set up your environment

You will need the following:

  • Epic Games launcher with Unreal Engine 4 or later

  • Visual Studio or any source code editor

  • Samsung Galaxy Z Fold2, Z Fold3, Z Flip, or Z Flip3

  • Remote Test Lab (if physical device is not available)


    • Samsung account
    • Java Runtime Environment (JRE) 7 or later with Java Web Start
    • Internet environment where port 2600 is available

Create and set up your project

After launching Unreal Engine from the Epic Games launcher, follow the steps below to start your project:

  1. In the Select or Create New Project window, choose Games as a new project category and click Next.

  1. Select Third Person as template, then click Next to proceed.

  1. In the Project Settings window, set the following:

    • Type of project: C++
    • Target platform: Mobile / Tablet
    • Performance characteristics: Scalable 3D or 2D
    • Real-time raytracing: Raytracing Disabled
    • Include an additional content pack: No Starter Content
    • Project name: Tutorial_Project

  1. Click Create Project. Wait for the engine to finish loading and open the Unreal Editor.

  1. Once the project is loaded, go to Edit > Project Settings > Platforms > Android.

  2. Click the Configure Now button if the project is not yet configured for the Android platform.

  3. Then, proceed with the following APK Packaging and Build settings:

    a. APK Packaging

    • Set Target SDK Version to 30.
    • Set Orientation to Full Sensor.
    • Change the Maximum supported aspect ratio to 2.8 (aspect ratio of Galaxy Z Fold3 in decimal) to prevent black bars from appearing on the cover display. Leave it if your game does not need to use the cover display.
    • Enable Use display cutout region?, to prevents black bars at the edge of the main screen. Otherwise, leave it unchecked.

    b. Build

    • Disable Support OpenGL ES3.1 and enable Support Vulkan.

Enable native resize event

The resize event of a game when switching between displays is disabled in the engine by default. However, this behavior can be easily enabled by setting Android.EnableNativeResizeEvent=1 in the DeviceProfile.

Currently, the only way to create a profile for foldable devices is by creating a specific rule for each device. To save time in this Code Lab, enable the native resize event for all Android devices instead.

  1. Locate and open the Tutorial_Project > Config folder in File Explorer.

  2. Inside the Config folder, create a new folder named Android.

  3. Create a new file called AndroidDeviceProfiles.ini and open this file in a text editor, such as Visual Studio.

  4. Copy below DeviceProfile code to the newly created AndroidDeviceProfiles.ini file.

    [Android DeviceProfile]
    ; Don't enable Vulkan by default. Specific device profiles can set this cvar to 0 to enable Vulkan.
    ; PF_B8G8R8A8
    ; PreviewAllowlistCVars and PreviewDenyListCVars are arrays of cvars that are included or excluded from being applied in mobile preview.
    ; If any PreviewAllowlistCVars is set, cvars are denied by default.

    This is a copy of the default Android DeviceProfile from the existing BaseDeviceProfiles.ini file but with the enabled NativeResizeEvent console variable (CVars).

Create a new plugin and import the FoldableHelper

FoldableHelper is a Java file that you can use in different projects. It provides an interface to the Android Jetpack WindowManager library, enabling application developers to support new device form factors and multi-window environments.

Before proceeding, read How to Use Jetpack WindowManager in Android Game Dev and learn the details of how FoldableHelper uses WindowManager library to retrieve information about the folded state of the device (FLAT for Normal mode and HALF-OPENED for Flex mode), window size, and orientation of the fold on the screen.

Download the file here:
(5.64 KB)

To import the file to the project, follow the steps below:

  1. Go to Edit > Plugins in the Unreal Editor.

  2. Click the New Plugin button and select Blank to create a blank plugin.

  3. In the Name field, type Foldables_Tutorial and click the Create Plugin button.

  1. In File Explorer, locate and open Tutorial_Project > Plugins folder.

  2. Go to Plugins > Foldables_Tutorial > Source> Foldables_Tutorial > Private and create a new folder called Java.

  3. Copy the file into Java folder.

  1. Open the Tutorial_Project.sln file in Visual Studio.

  2. In the same Private folder path, add a new filter called Java.

  3. Right-click on the Java filter and click Add > Existing Item. Locate the file, then click Add to include this Java file in the build.

Modify Java Activity to use FoldableHelper

Unreal Plugin Language (UPL) is a simple XML-based language created by Epic Games for manipulating XML and returning strings.

Using UPL, you can utilize the file by modifying the Java Activity and related Gradle files as follows:

  1. In Visual Studio, right-click on Source > Foldables_Tutorial folder, then click Add > New Item > Web > XML File (.xml).

  2. Create an XML file called Foldables_Tutorial_UPL.xml.

  3. Ensure that the file location is correct before clicking Add.

  1. In the newly created XML file, include the file in the build by copying the Java folder to the build directory.

    <root xmlns:android="">
    		<copyDir src="$S(PluginDir)/Private/Java" dst="$S(BuildDir)/src/com/samsung/android/gamedev/foldable" />

  1. Set up the Gradle dependencies in the build.gradle file by adding the following in the XML file:

    			dependencies {
    			implementation fileTree(dir: 'libs', include: ['*.jar'])
    			implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.0"
    			implementation "androidx.core:core:1.7.0"
    			implementation "androidx.core:core-ktx:1.7.0"
    			implementation "androidx.appcompat:appcompat:1.4.0"
    			implementation "androidx.window:window:1.0.0"
    			implementation "androidx.window:window-java:1.0.0"
    			sourceCompatibility JavaVersion.VERSION_1_8
    			targetCompatibility JavaVersion.VERSION_1_8

  1. Next, modify the gameActivity:

    			<!-- package name of FoldableHelper -->	
    • gameActivityImportAdditions adds the into the gameActivity with the existing imports.
    • gameActivityOnCreateAdditions adds the code to the OnCreate() method inside the gameActivity.
    • gameActivityOnStartAdditions adds the code to the OnStart() method inside the gameActivity.
    • gameActivityOnStopAdditions adds the code to the OnStop() method inside the gameActivity.
  2. Save the XML file.

  3. Then, ensure that the engine uses the UPL file by modifying the Foldables_Tutorial.Build.cs script, located in the same folder as the Foldables_Tutorial_UPL.xml file. After the DynamicallyLoadedModuleNames.AddRange call, add the following:

    if (Target.Platform == UnrealTargetPlatform.Android)
    	AdditionalPropertiesForReceipt.Add("AndroidPlugin", ModuleDirectory + "\\Foldables_Tutorial_UPL.xml");

    This means that the game engine will use the UPL file if the platform is Android. Otherwise, the FoldableHelper won’t work.

Implement a storage struct

The next thing to implement is a struct, the native version of Java’s FoldableLayoutInfo class.

To store the data retrieved from the Java code using a struct, do the following:

  1. In Content Browser of Unreal Editor, right-click on C++ Classes > Add/Import Content. Then, click New C++ Class.

  2. Select None for the parent class and click Next.

  3. Name the new class as FoldableLayoutInfo.

  4. Assign it to the Foldables_Tutorial plugin. Then, click Create Class.

  1. Delete the created FoldableLayoutInfo.cpp file and only keep its header file.

  2. In the header file called FoldableLayoutInfo.h, set up a struct to store all needed data from the WindowManager.

    #pragma once
    #include "Core.h"
    enum EFoldOcclusionType { UNDEFINED_OCCLUSION, NONE, FULL };
    struct FFoldableLayoutInfo
    	EFoldState State;
    	EFoldOrientation Orientation;
    	EFoldOcclusionType OcclusionType;
    	FVector4 FoldBounds;
    	FVector4 CurrentMetrics;
    	FVector4 MaxMetrics;
    	bool IsSeparating;
    	FFoldableLayoutInfo() :
    		FoldBounds(-1, -1, -1, -1),
    		CurrentMetrics(-1, -1, -1, -1),
    		MaxMetrics(-1, -1, -1, -1),
    	{	}

Implement JNI code

To implement JNI, create a New C++ Class with no parent and name it Foldables_Helper. Assign the class to the same plugin, then modify the C++ header and source files as follows:

  1. In the created header file (Foldables_Helper.h), include FoldableLayoutInfo.h.

    #include "FoldableLayoutInfo.h"

  1. Then, declare a MULTICAST_DELEGATE to serve as a listener for passing the data from the Java implementation to the rest of the engine.

    DECLARE_MULTICAST_DELEGATE_OneParam(FOnLayoutChangedDelegate, FFoldableLayoutInfo);

  1. Lastly, set up the methods and member variables.

    class FOLDABLES_TUTORIAL_API FFoldables_Helper
    	static void Init();
    	static bool HasListener;
    	static FOnLayoutChangedDelegate OnLayoutChanged;

  1. Moving to the source file (Foldables_Helper.cpp), set up the definitions for the methods and member variables created in the header file.

    bool FFoldables_Helper::HasListener = false;
    FOnLayoutChangedDelegate FFoldables_Helper::OnLayoutChanged;
    void FFoldables_Helper::Init() {
    	HasListener = true;

  1. Now, in the same source file, create the native version of the OnLayoutChanged() function created in the file.

  2. Since the Java OnLayoutChanged() function only works on Android, surround the function with an #if directive to ensure that it compiles only on Android.


  1. Within this directive, copy the code below to use the JNI definition of the Java OnLayoutChanged() function.

    extern "C"
    Java_com_samsung_android_gamedev_foldable_FoldableHelper_OnLayoutChanged(JNIEnv * env, jclass clazz,
    	jobject JfoldableLayoutInfo) {

  1. Create the FFoldableLayoutInfo to store the data retrieved from Java.

    FFoldableLayoutInfo result;

  1. Retrieve the Field IDs of the FoldableLayoutInfo and Rect Objects created in the Java file.

    //Java FoldableLayoutInfo Field IDs
    jclass jfoldableLayoutInfoCls = env->GetObjectClass(JfoldableLayoutInfo);
    jfieldID currentMetricsId = env->GetFieldID(jfoldableLayoutInfoCls, "currentMetrics", "Landroid/graphics/Rect;");
    jfieldID maxMetricsId = env->GetFieldID(jfoldableLayoutInfoCls, "maxMetrics", "Landroid/graphics/Rect;");
    jfieldID hingeOrientationId = env->GetFieldID(jfoldableLayoutInfoCls, "hingeOrientation", "I");
    jfieldID stateId = env->GetFieldID(jfoldableLayoutInfoCls, "state", "I");
    jfieldID occlusionTypeId = env->GetFieldID(jfoldableLayoutInfoCls, "occlusionType", "I");
    jfieldID isSeparatingId = env->GetFieldID(jfoldableLayoutInfoCls, "isSeparating", "Z");
    jfieldID boundsId = env->GetFieldID(jfoldableLayoutInfoCls, "bounds", "Landroid/graphics/Rect;");
    jobject currentMetricsRect = env->GetObjectField(JfoldableLayoutInfo, currentMetricsId);
    //Java Rect Object Field IDs
    jclass rectCls = env->GetObjectClass(currentMetricsRect);
    jfieldID leftId = env->GetFieldID(rectCls, "left", "I");
    jfieldID topId = env->GetFieldID(rectCls, "top", "I");
    jfieldID rightId = env->GetFieldID(rectCls, "right", "I");
    jfieldID bottomId = env->GetFieldID(rectCls, "bottom", "I");

  1. Retrieve the current WindowMetrics and store it in the FFoldableLayoutInfo as an FIntVector4.

    // currentMetrics
    int left = env->GetIntField(currentMetricsRect, leftId);
    int top = env->GetIntField(currentMetricsRect, topId);
    int right = env->GetIntField(currentMetricsRect, rightId);
    int bottom = env->GetIntField(currentMetricsRect, bottomId);
    // Store currentMetrics Rect to FVector4
    result.CurrentMetrics = FIntVector4{ left, top, right, bottom };

  1. Do the same for the other variables in the Java FoldableLayoutInfo.

    // maxMetrics
    jobject maxMetricsRect = env->GetObjectField(JfoldableLayoutInfo, maxMetricsId);
    left = env->GetIntField(maxMetricsRect, leftId);
    top = env->GetIntField(maxMetricsRect, topId);
    right = env->GetIntField(maxMetricsRect, rightId);
    bottom = env->GetIntField(maxMetricsRect, bottomId);
    //Store maxMetrics Rect to FVector4
    result.MaxMetrics = FIntVector4{ left, top, right, bottom };
    int hingeOrientation = env->GetIntField(JfoldableLayoutInfo, hingeOrientationId);
    int state = env->GetIntField(JfoldableLayoutInfo, stateId);
    int occlusionType = env->GetIntField(JfoldableLayoutInfo, occlusionTypeId);
    bool isSeparating = env->GetBooleanField(JfoldableLayoutInfo, isSeparatingId);
    // Store the values to an object for Unreal
    result.Orientation = TEnumAsByte<EFoldOrientation>(hingeOrientation + 1);
    result.State = TEnumAsByte<EFoldState>(state + 1);
    result.OcclusionType = TEnumAsByte<EFoldOcclusionType>(occlusionType + 1);
    result.IsSeparating = isSeparating;
    // boundsRect
    jobject boundsRect = env->GetObjectField(JfoldableLayoutInfo, boundsId);
    left = env->GetIntField(boundsRect, leftId);
    top = env->GetIntField(boundsRect, topId);
    right = env->GetIntField(boundsRect, rightId);
    bottom = env->GetIntField(boundsRect, bottomId);
    // Store maxMetrics Rect to FVector4
    result.FoldBounds = FIntVector4{ left, top, right, bottom };

  1. Broadcast the result via the OnLayoutChanged delegate for use in the engine.

        if (FFoldables_Helper::HasListener)
       	 UE_LOG(LogTemp, Warning, TEXT("Broadcast"));

Create a player controller and two UI states

This section focuses on adding a player controller and creating two user interface (UI) states for Flat and Flex modes. These objects are needed for the Flex mode logic implementation. Following are the steps to add a player controller and create two UI states :

  1. Add a new Player Controller Blueprint. In Content Browser, go to Content > ThirdPersonCPP and right-click on Blueprints > Add/Import Content > Blueprint Class.

  2. Pick Player Controller as its parent class.

  3. Rename it as FlexPlayerController.

  1. Add a New C++ class with Actor Component as its parent class.

  1. Name it as Foldables_Manager and assign it to the Foldables_Tutorial plugin. Click the Create Class button.

  1. Open the FlexPlayerController Blueprint by double-clicking it.

  2. Click Open Full Blueprint Editor.

  1. Attach the Actor Component to the FlexPlayerController. In the left pane, click Add Component, then find and select the Foldables_Manager.

  1. Next, create a pair of UserWidget classes for the UI layouts needed: Flat mode UI for the full screen or Normal mode; and Flex mode UI for split-screen.

  2. In Add C++ Class window, select the Show All Classes checkbox.

  3. Find and pick UserWidget as the parent class. Then, click Next.

  1. Name the new User Widget as FlatUI and attach it to the plugin. Click Next.

  1. Repeat the process but name the new User Widget as FlexUI.

  2. You might get an error when trying to compile stating that the UserWidget is an undeclared symbol. To fix this, open the Foldables_Tutorial.Build.cs file, and in the PublicDependencyModuleNames.AddRange call, add "InputCore" and "UMG" to the list.

  3. Create a pair of Blueprints made from subclasses of these two User Widgets.

  4. Right-click on Content and create a New Folder called FoldUI.

  1. Inside the newly created folder, right-click to add a new Blueprint Class.

  2. In All Classes, choose FlatUI and click the Select button.

  1. Rename the Blueprint as BP_FlatUI.

  2. In the same folder, repeat the process but choose the FlexUI class and rename the Blueprint as BP_FlexUI.

  1. Double-click on BP_FlatUI and BP_FlexUI, then design your two UIs like below to visualize switching between Flat and Flex mode:

    • Flat UI

    • Flex UI

Implement the Flex mode logic

After creating the FlexPlayerController and the two UI states (BP_FlatUI and BP_FlexUI), you can now implement Flex mode logic in the Foldables_Manager.

  1. Open the Foldables_Manager.h and include the necessary C++ header files:

    #pragma once
    #include "CoreMinimal.h"
    #include "Components/ActorComponent.h"
    #include "Engine.h"
    #include "FlatUI.h"
    #include "FlexUI.h"
    #include "Foldables_Helper.h"
    #include "Foldables_Manager.generated.h"

  1. Remove the line below to save a little bit of performance as this Component doesn't need to tick.

    // Called every frame
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

  1. Set up the functions needed in Foldables_Manager:

    • The constructor, a function to create the UI Widgets
    • The implementation of OnLayoutChanged delegate
    	// Sets default values for this component's properties
    	void CreateWidgets();
    	// Called when the game starts
    	virtual void BeginPlay() override;
    	void OnLayoutChanged(FFoldableLayoutInfo FoldableLayoutInfo);

  1. Then, set up the variables needed:

    • References to the Flat and Flex UI Classes
    • References to the Flat and Flex UI Objects
  2. Mark the pointers as UPROPERTY to ensure that garbage collection does not delete the objects they point to.

    TSubclassOf<UUserWidget> FlatUIClass;
    TSubclassOf<UUserWidget> FlexUIClass;
    	class UFlatUI* FlatUI;
    	class UFlexUI* FlexUI;

  1. Finally, define a new private function RestoreFlatMode(), to disable Flex mode and return to Normal mode.

    	void RestoreFlatMode();

  1. Moving over to Foldables_Manager.cpp, implement the constructor. Using the ConstructorHelpers, find the UI classes and set the variables to store these classes.

  2. Also, set the bCanEverTick to false to prevent the component from ticking and remove the code block of TickComponent() function.

    // Sets default values for this component's properties
    	PrimaryComponentTick.bCanEverTick = false;
    	static ConstructorHelpers::FClassFinder<UFlatUI> FlatUIBPClass(TEXT("/Game/FoldUI/BP_FlatUI"));
    	static ConstructorHelpers::FClassFinder<UFlexUI> FlexUIBPClass(TEXT("/Game/FoldUI/BP_FlexUI"));
    	if (FlatUIBPClass.Succeeded())
    		FlatUIClass = FlatUIBPClass.Class;
    	if (FlexUIBPClass.Succeeded())
    		FlexUIClass = FlexUIBPClass.Class;

  1. Next, set up the BeginPlay() function to link the delegate to the OnLayoutChanged() function, to initialize the Foldables_Helper, and to create the Widgets ready for use in the first frame.

    // Called when the game starts
    void UFoldables_Manager::BeginPlay()
    	FFoldables_Helper::OnLayoutChanged.AddUObject(this, &UFoldables_Manager::OnLayoutChanged);

  1. Set up the CreateWidgets() function to create the Widgets using the UI classes acquired in the constructor.

  2. Add the FlatUI Widget to the Viewport, assuming the app opens in Normal mode until it receives the FoldableLayoutInfo.

    void UFoldables_Manager::CreateWidgets()
    	FlatUI = CreateWidget<UFlatUI>((APlayerController*)GetOwner(), FlatUIClass, FName(TEXT("FlatUI")));
    	FlexUI = CreateWidget<UFlexUI>((APlayerController*)GetOwner(), FlexUIClass, FName(TEXT("FlexUI")));

  1. Afterward, create the OnLayoutChanged() function, which will be called via a delegate.

  2. Inside this function, check whether the device’s folded state is HALF_OPENED.

  3. If so, check whether the orientation of the fold is HORIZONTAL.

    void UFoldables_Manager::OnLayoutChanged(FFoldableLayoutInfo FoldableLayoutInfo)
    	//If state is now Flex
    	if (FoldableLayoutInfo.State == EFoldState::HALF_OPENED)
    		if (FoldableLayoutInfo.Orientation == EFoldOrientation::HORIZONTAL)

  1. If the device is both on Flex mode and horizontal fold, change the Viewport to only render on the top screen using the normalized position of the folding feature.

  2. Then in an AsyncTask on the game thread, disable the FlatUI and enable the FlexUI.

  3. However, if the device is on Normal mode, then return to Flat UI using RestoreFlatMode() function.

        		//Horizontal Split
        		float foldRatio = (float)FoldableLayoutInfo.FoldBounds.Y / (FoldableLayoutInfo.CurrentMetrics.W - FoldableLayoutInfo.CurrentMetrics.Y);
        		GEngine->GameViewport->SplitscreenInfo[0].PlayerData[0].SizeX = 1.0f;
        		GEngine->GameViewport->SplitscreenInfo[0].PlayerData[0].SizeY = foldRatio;
        		AsyncTask(ENamedThreads::GameThread, [=]()
        				if (FlatUI->IsInViewport())
        				if (!FlexUI->IsInViewport())

  1. Reverse the Flex mode implementation logic to create the RestoreFlatMode() function by setting the Viewport to fill the screen, then disable the FlexUI and enable the FlatUI.

    void UFoldables_Manager::RestoreFlatMode()
    	GEngine->GameViewport->SplitscreenInfo[0].PlayerData[0].SizeX = 1.0f;
    	GEngine->GameViewport->SplitscreenInfo[0].PlayerData[0].SizeY = 1.0f;
    	AsyncTask(ENamedThreads::GameThread, [=]()
    			if (!FlatUI->IsInViewport())
    			if (FlexUI->IsInViewport())

Set up a Game Mode and attach the FlexPlayerController

The Game Mode defines the game rules, scoring, and any game-specific behavior. Set up the Game Mode in Unreal Editor by creating a Blueprint Class in the Content > ThirdPersonCPP > Blueprints folder.

Pick Game Mode Base as the parent class and rename it as FlexGameMode.

Double-click on FlexGameMode. In the drop-down menu next to Player Controller Class, choose the FlexPlayerController.

Lastly, go to Edit > Project Settings > Project > Maps & Modes and select FlexGameMode as the Default GameMode.

Build and run the app

Go to Edit > Package Project > Android to build the APK. Ensure that the Android development environment for Unreal is already set up to your computer.

After packaging your Android project, run the game app on a foldable Galaxy device and see how the UI switches from Normal to Flex mode. If you don’t have any physical device, you can also test it on a Remote Test Lab device.

You're done!

Congratulations! You have successfully achieved the goal of this Code Lab. Now, you can implement Flex mode in your Unreal Engine game app by yourself! If you're having trouble, you may download this file:

Flex Mode on Unreal Complete Code
(20.16 MB)

To learn more, visit: