Proper way to provide custom activity or extend GameActivity

After a long time searching, I can’t find any instructions on how to provide custom platform specific (Java) code on Android. I succeeded on using jni in my C++ code to access the Java object for the GameActivity class provided by UE4, however, I’d like to either extend that class or provide a custom main activity based on it specific to my game, i.e., I don’t want to change the file inside the engine folder. What’s the proper way to do that? Is there anyway to include additional Java sources in the build process?

Thanks in advance!

I’ve not yet dug into it myself, but the closest thing to an answer I’ve found is the advice to look at the Gear VR and Google VR plugins. Hope this helps, I’m at the start of figuring this out, too.

OK, so I didn’t find a way to create separate classes, but the UPL (unreal plugin language) allows one highly customize the generated GameActivity class. You can find the full documentation of the tags accepted by the UPL in the source file. Here I’ll give an example using my application. Basically I wanted to access the location sensor, so I created a file called LocationService_UPL.xml on the root of the game source (here my game is called Otoshiyori):


Project\Source\Otoshiyori\LocationService_UPL.xml:

<?xml version="1.0" encoding="utf-8"?>
<root xmlns:android="http://schemas.android.com/apk/res/android">
  <init>
    <log text="LocationService additions init"/>
  </init>

  <proguardAdditions>
    <insert>
    </insert>
  </proguardAdditions>

  <androidManifestUpdates>
    <addPermission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <addPermission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <addFeature android:name="android.hardware.location.gps" />
    <addFeature android:name="android.hardware.location.network" />
  </androidManifestUpdates>

  <gameActivityImportAdditions>
    <insert>
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
    </insert>
  </gameActivityImportAdditions>

  <gameActivityOnCreateAdditions>
    <insert>
    locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
    locationListener = new Otoshiyori_LocationListener();
    </insert>
  </gameActivityOnCreateAdditions>

  <gameActivityOnPauseAdditions>
    <insert>
    if (locationManager != null)
        locationManager.removeUpdates(locationListener);
    </insert>
  </gameActivityOnPauseAdditions>

  <gameActivityOnResumeAdditions>
    <insert>
    String locationProvider = getLocationProvider();
    if (locationProvider != null)
        locationManager.requestLocationUpdates(locationProvider, 500, 5, locationListener);
    </insert>
  </gameActivityOnResumeAdditions>
  
  <gameActivityClassAdditions>
    <insert>
    private LocationManager locationManager;
    private Otoshiyori_LocationListener locationListener;

    private String getLocationProvider() {
        final Criteria locationCriteria = new Criteria() {
            {
                setHorizontalAccuracy(ACCURACY_HIGH);
            }
        };
        String provider = null;
        if (locationManager != null) {
            provider = locationManager.getBestProvider(locationCriteria, true);
        }
        return provider;
    }

    private class Otoshiyori_LocationListener implements LocationListener {
        public void onLocationChanged(Location location) {
            Otoshiyori_LocationChanged(location);
        }

        public void onStatusChanged(String provider, int status, Bundle extras) {}

        public void onProviderEnabled(String provider) {}

        public void onProviderDisabled(String provider) {}
    }

    native void Otoshiyori_LocationChanged(Location location);
    </insert>
  </gameActivityClassAdditions>
</root>

In the Otoshioyori.Build.cs file, I added the following code to the constructor, so it will use my UPL file to customize the generated game activity:

if (Target.Platform == UnrealTargetPlatform.Android)
{
    PrivateDependencyModuleNames.AddRange(new string[] { "Launch" });

    PrivateIncludePaths.Add("Otoshiyori/Private/Android");

    string UPLPath = Utils.MakePathRelativeTo(ModuleDirectory, BuildConfiguration.RelativeEnginePath);
    AdditionalPropertiesForReceipt.Add(new ReceiptProperty("AndroidPlugin", Path.Combine(UPLPath, "LocationService_UPL.xml")));
}

Finally in one of my source files I implemented the native method following the jni standard naming conventions:

extern "C"
{
    void Java_com_epicgames_ue4_GameActivity_Otoshiyori_1LocationChanged(JNIEnv*, jobject, jobject);
}

/* ... */

void Java_com_epicgames_ue4_GameActivity_Otoshiyori_1LocationChanged(JNIEnv* jenv, jobject thiz, jobject location)
{
    /* use JNI to handle the received data... */
}

I hope this helps.