Implement support for Play Store builds using Play Feature Delivery (Java/Gradle)
This commit is contained in:
parent
54c31348ce
commit
31e8fe8fd9
|
@ -1,5 +1,14 @@
|
||||||
package com.retroarch.browser.retroactivity;
|
package com.retroarch.browser.retroactivity;
|
||||||
|
|
||||||
|
import com.google.android.play.core.splitinstall.SplitInstallManager;
|
||||||
|
import com.google.android.play.core.splitinstall.SplitInstallManagerFactory;
|
||||||
|
import com.google.android.play.core.splitinstall.SplitInstallRequest;
|
||||||
|
import com.google.android.play.core.splitinstall.SplitInstallSessionState;
|
||||||
|
import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener;
|
||||||
|
import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus;
|
||||||
|
import com.google.android.play.core.tasks.OnFailureListener;
|
||||||
|
import com.google.android.play.core.tasks.OnSuccessListener;
|
||||||
|
import com.retroarch.BuildConfig;
|
||||||
import com.retroarch.browser.preferences.util.UserPreferences;
|
import com.retroarch.browser.preferences.util.UserPreferences;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.app.NativeActivity;
|
import android.app.NativeActivity;
|
||||||
|
@ -10,6 +19,8 @@ import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.media.AudioAttributes;
|
import android.media.AudioAttributes;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.system.Os;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
@ -20,7 +31,14 @@ import android.os.PowerManager;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
import android.os.VibrationEffect;
|
import android.os.VibrationEffect;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import java.lang.Math;
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
@ -40,9 +58,60 @@ public class RetroActivityCommon extends NativeActivity
|
||||||
public static int FRONTEND_ORIENTATION_270 = 3;
|
public static int FRONTEND_ORIENTATION_270 = 3;
|
||||||
public static int RETRO_RUMBLE_STRONG = 0;
|
public static int RETRO_RUMBLE_STRONG = 0;
|
||||||
public static int RETRO_RUMBLE_WEAK = 1;
|
public static int RETRO_RUMBLE_WEAK = 1;
|
||||||
|
public static int INSTALL_STATUS_DOWNLOADING = 0;
|
||||||
|
public static int INSTALL_STATUS_INSTALLING = 1;
|
||||||
|
public static int INSTALL_STATUS_INSTALLED = 2;
|
||||||
|
public static int INSTALL_STATUS_FAILED = 3;
|
||||||
public boolean sustainedPerformanceMode = true;
|
public boolean sustainedPerformanceMode = true;
|
||||||
public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
|
public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
|
||||||
|
|
||||||
|
private final SplitInstallStateUpdatedListener listener = new SplitInstallStateUpdatedListener() {
|
||||||
|
@Override
|
||||||
|
public void onStateUpdate(SplitInstallSessionState state) {
|
||||||
|
List<String> moduleNames = state.moduleNames();
|
||||||
|
String[] coreNames = new String[moduleNames.size()];
|
||||||
|
|
||||||
|
for(int i = 0; i < moduleNames.size(); i++) {
|
||||||
|
coreNames[i] = unsanitizeCoreName(moduleNames.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(state.status()) {
|
||||||
|
case SplitInstallSessionStatus.DOWNLOADING:
|
||||||
|
coreInstallStatusChanged(coreNames, INSTALL_STATUS_DOWNLOADING, state.bytesDownloaded(), state.totalBytesToDownload());
|
||||||
|
break;
|
||||||
|
case SplitInstallSessionStatus.INSTALLING:
|
||||||
|
coreInstallStatusChanged(coreNames, INSTALL_STATUS_INSTALLING, state.bytesDownloaded(), state.totalBytesToDownload());
|
||||||
|
break;
|
||||||
|
case SplitInstallSessionStatus.INSTALLED:
|
||||||
|
updateSymlinks();
|
||||||
|
|
||||||
|
coreInstallStatusChanged(coreNames, INSTALL_STATUS_INSTALLED, state.bytesDownloaded(), state.totalBytesToDownload());
|
||||||
|
break;
|
||||||
|
case SplitInstallSessionStatus.FAILED:
|
||||||
|
coreInstallStatusChanged(coreNames, INSTALL_STATUS_FAILED, state.bytesDownloaded(), state.totalBytesToDownload());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
updateSymlinks();
|
||||||
|
|
||||||
|
SplitInstallManager manager = SplitInstallManagerFactory.create(this);
|
||||||
|
manager.registerListener(listener);
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
SplitInstallManager manager = SplitInstallManagerFactory.create(this);
|
||||||
|
manager.unregisterListener(listener);
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
public void doVibrate(int id, int effect, int strength, int oneShot)
|
public void doVibrate(int id, int effect, int strength, int oneShot)
|
||||||
{
|
{
|
||||||
Vibrator vibrator = null;
|
Vibrator vibrator = null;
|
||||||
|
@ -297,4 +366,240 @@ public class RetroActivityCommon extends NativeActivity
|
||||||
|
|
||||||
Log.i("RetroActivity", "hasOldOrientation? " + hasOldOrientation + " newOrientation: " + newConfig.orientation + " oldOrientation: " + oldOrientation);
|
Log.i("RetroActivity", "hasOldOrientation? " + hasOldOrientation + " newOrientation: " + newConfig.orientation + " oldOrientation: " + oldOrientation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this version of RetroArch is a Play Store build.
|
||||||
|
*
|
||||||
|
* @return true if this is a Play Store build, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isPlayStoreBuild() {
|
||||||
|
Log.i("RetroActivity", "isPlayStoreBuild: " + BuildConfig.PLAY_STORE_BUILD);
|
||||||
|
|
||||||
|
return BuildConfig.PLAY_STORE_BUILD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of available cores that can be downloaded as Dynamic Feature Modules.
|
||||||
|
*
|
||||||
|
* @return the list of available cores
|
||||||
|
*/
|
||||||
|
public String[] getAvailableCores() {
|
||||||
|
int id = getResources().getIdentifier("module_names_" + sanitizeCoreName(Build.CPU_ABI), "array", getPackageName());
|
||||||
|
|
||||||
|
String[] returnVal = getResources().getStringArray(id);
|
||||||
|
Log.i("RetroActivity", "getAvailableCores: " + Arrays.toString(returnVal));
|
||||||
|
return returnVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of cores that are currently installed as Dynamic Feature Modules.
|
||||||
|
*
|
||||||
|
* @return the list of installed cores
|
||||||
|
*/
|
||||||
|
public String[] getInstalledCores() {
|
||||||
|
SplitInstallManager manager = SplitInstallManagerFactory.create(this);
|
||||||
|
String[] modules = manager.getInstalledModules().toArray(new String[0]);
|
||||||
|
List<String> cores = new ArrayList<>();
|
||||||
|
|
||||||
|
SharedPreferences prefs = UserPreferences.getPreferences(this);
|
||||||
|
|
||||||
|
for(int i = 0; i < modules.length; i++) {
|
||||||
|
String coreName = unsanitizeCoreName(modules[i]);
|
||||||
|
if(!prefs.getBoolean("core_deleted_" + coreName, false)) {
|
||||||
|
cores.add(coreName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] returnVal = cores.toArray(new String[0]);
|
||||||
|
Log.i("RetroActivity", "getInstalledCores: " + Arrays.toString(returnVal));
|
||||||
|
return returnVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asks the system to download a core.
|
||||||
|
*
|
||||||
|
* @param coreName Name of the core to install
|
||||||
|
*/
|
||||||
|
public void downloadCore(final String coreName) {
|
||||||
|
Log.i("RetroActivity", "downloadCore: " + coreName);
|
||||||
|
|
||||||
|
SharedPreferences prefs = UserPreferences.getPreferences(this);
|
||||||
|
prefs.edit().remove("core_deleted_" + coreName).apply();
|
||||||
|
|
||||||
|
SplitInstallManager manager = SplitInstallManagerFactory.create(this);
|
||||||
|
SplitInstallRequest request = SplitInstallRequest.newBuilder()
|
||||||
|
.addModule(sanitizeCoreName(coreName))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
manager.startInstall(request)
|
||||||
|
.addOnSuccessListener(new OnSuccessListener<Integer>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Integer result) {
|
||||||
|
coreInstallInitiated(coreName, true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.addOnFailureListener(new OnFailureListener() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Exception e) {
|
||||||
|
coreInstallInitiated(coreName, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asks the system to delete a core.
|
||||||
|
*
|
||||||
|
* Note that the actual module deletion will not happen immediately (the OS will delete
|
||||||
|
* it whenever it feels like it), but the symlink will still be immediately removed.
|
||||||
|
*
|
||||||
|
* @param coreName Name of the core to delete
|
||||||
|
*/
|
||||||
|
public void deleteCore(String coreName) {
|
||||||
|
Log.i("RetroActivity", "deleteCore: " + coreName);
|
||||||
|
|
||||||
|
String newFilename = getCorePath() + coreName + "_libretro_android.so";
|
||||||
|
new File(newFilename).delete();
|
||||||
|
|
||||||
|
SharedPreferences prefs = UserPreferences.getPreferences(this);
|
||||||
|
prefs.edit().putBoolean("core_deleted_" + coreName, true).apply();
|
||||||
|
|
||||||
|
SplitInstallManager manager = SplitInstallManagerFactory.create(this);
|
||||||
|
manager.deferredUninstall(Collections.singletonList(sanitizeCoreName(coreName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/////////////// JNI methods ///////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a core install is initiated.
|
||||||
|
*
|
||||||
|
* @param coreName Name of the core that the install is initiated for.
|
||||||
|
* @param successful true if success, false if failure
|
||||||
|
*/
|
||||||
|
private native void coreInstallInitiated(String coreName, boolean successful);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the status of a core install has changed.
|
||||||
|
*
|
||||||
|
* @param coreNames Names of all cores that are currently being downloaded.
|
||||||
|
* @param status One of INSTALL_STATUS_DOWNLOADING, INSTALL_STATUS_INSTALLING,
|
||||||
|
* INSTALL_STATUS_INSTALLED, or INSTALL_STATUS_FAILED
|
||||||
|
* @param bytesDownloaded Number of bytes downloaded.
|
||||||
|
* @param totalBytesToDownload Total number of bytes to download.
|
||||||
|
*/
|
||||||
|
private native void coreInstallStatusChanged(String[] coreNames, int status, long bytesDownloaded, long totalBytesToDownload);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/////////////// Private methods ///////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes a core name so that it can be used when dealing with
|
||||||
|
* Dynamic Feature Modules. Needed because Gradle modules cannot use
|
||||||
|
* dashes, but we have at least one core name ("mesen-s") that uses them.
|
||||||
|
*
|
||||||
|
* @param coreName Name of the core to sanitize.
|
||||||
|
* @return The sanitized core name.
|
||||||
|
*/
|
||||||
|
private String sanitizeCoreName(String coreName) {
|
||||||
|
return coreName.replace('-', '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsanitizes a core name from its module name.
|
||||||
|
*
|
||||||
|
* @param coreName Name of the core to unsanitize.
|
||||||
|
* @return The unsanitized core name.
|
||||||
|
*/
|
||||||
|
private String unsanitizeCoreName(String coreName) {
|
||||||
|
if(coreName.equals("mesen_s")) {
|
||||||
|
return "mesen-s";
|
||||||
|
}
|
||||||
|
|
||||||
|
return coreName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the path to the RetroArch cores directory.
|
||||||
|
*
|
||||||
|
* @return The path to the RetroArch cores directory
|
||||||
|
*/
|
||||||
|
private String getCorePath() {
|
||||||
|
return getApplicationInfo().dataDir + "/cores/";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a symlink update in the known places that Dynamic Feature Modules
|
||||||
|
* are installed to.
|
||||||
|
*/
|
||||||
|
private void updateSymlinks() {
|
||||||
|
traverseFilesystem(getFilesDir());
|
||||||
|
traverseFilesystem(new File(getApplicationInfo().nativeLibraryDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traverse the filesystem, looking for native libraries.
|
||||||
|
* Symlinks any libraries it finds to the main RetroArch "cores" folder,
|
||||||
|
* updating any existing symlinks with the correct path to the native libraries.
|
||||||
|
*
|
||||||
|
* This is necessary because Dynamic Feature Modules are first downloaded
|
||||||
|
* and installed to a temporary location on disk, before being moved
|
||||||
|
* to a more permanent location by the system at a later point.
|
||||||
|
*
|
||||||
|
* This could probably be done in native code instead, if that's preferred.
|
||||||
|
*
|
||||||
|
* @param file The parent directory of the tree to traverse.
|
||||||
|
* @param cores List of cores to update.
|
||||||
|
* @param filenames List of filenames to update.
|
||||||
|
*/
|
||||||
|
private void traverseFilesystem(File file) {
|
||||||
|
File[] list = file.listFiles();
|
||||||
|
if(list == null) return;
|
||||||
|
|
||||||
|
// Check each file in a directory to see if it's a native library.
|
||||||
|
for(int i = 0; i < list.length; i++) {
|
||||||
|
File child = list[i];
|
||||||
|
String name = child.getName();
|
||||||
|
|
||||||
|
if(name.startsWith("lib") && name.endsWith(".so") && !name.contains("retroarch-activity")) {
|
||||||
|
// Found a native library!
|
||||||
|
String core = name.subSequence(3, name.length() - 3).toString();
|
||||||
|
String filename = child.getAbsolutePath();
|
||||||
|
|
||||||
|
SharedPreferences prefs = UserPreferences.getPreferences(this);
|
||||||
|
if(!prefs.getBoolean("core_deleted_" + core, false)) {
|
||||||
|
// Generate the destination filename and delete any existing symlinks / cores
|
||||||
|
String newFilename = getCorePath() + core + "_libretro_android.so";
|
||||||
|
new File(newFilename).delete();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// On Android 5.0+, use the official API for creating a symlink.
|
||||||
|
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
Os.symlink(filename, newFilename);
|
||||||
|
} else {
|
||||||
|
// On older versions, resort to using reflection instead.
|
||||||
|
Class<?> clazz = Class.forName("libcore.io.Libcore");
|
||||||
|
Field field = clazz.getDeclaredField("os");
|
||||||
|
field.setAccessible(true);
|
||||||
|
|
||||||
|
Object os = field.get(null);
|
||||||
|
Method method = os.getClass().getMethod("symlink", String.class, String.class);
|
||||||
|
method.invoke(os, filename, newFilename);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Symlink failed to be created. Should never happen.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(file.isDirectory()) {
|
||||||
|
// Found another directory, so traverse it
|
||||||
|
traverseFilesystem(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,10 @@
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
build
|
build
|
||||||
phoenix.iml
|
phoenix.iml
|
||||||
|
output.json
|
||||||
|
keystore.properties
|
||||||
|
modules/
|
||||||
|
settings.gradle
|
||||||
|
dynamic_features.gradle
|
||||||
|
res/values/core_names.xml
|
||||||
|
res/values/module_names_*.xml
|
||||||
|
|
|
@ -16,12 +16,14 @@
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name="com.google.android.play.core.splitcompat.SplitCompatApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:hasCode="true"
|
android:hasCode="true"
|
||||||
android:isGame="true"
|
android:isGame="true"
|
||||||
android:banner="@drawable/banner"
|
android:banner="@drawable/banner"
|
||||||
|
android:extractNativeLibs="true"
|
||||||
tools:ignore="UnusedAttribute">
|
tools:ignore="UnusedAttribute">
|
||||||
<activity android:name="com.retroarch.browser.mainmenu.MainMenuActivity" android:exported="true" android:launchMode="singleInstance">
|
<activity android:name="com.retroarch.browser.mainmenu.MainMenuActivity" android:exported="true" android:launchMode="singleInstance">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
|
@ -37,12 +37,14 @@ android {
|
||||||
productFlavors {
|
productFlavors {
|
||||||
normal {
|
normal {
|
||||||
resValue "string", "app_name", "RetroArch"
|
resValue "string", "app_name", "RetroArch"
|
||||||
|
buildConfigField "boolean", "PLAY_STORE_BUILD", "false"
|
||||||
|
|
||||||
dimension "variant"
|
dimension "variant"
|
||||||
}
|
}
|
||||||
aarch64 {
|
aarch64 {
|
||||||
applicationIdSuffix '.aarch64'
|
applicationIdSuffix '.aarch64'
|
||||||
resValue "string", "app_name", "RetroArch (AArch64)"
|
resValue "string", "app_name", "RetroArch (AArch64)"
|
||||||
|
buildConfigField "boolean", "PLAY_STORE_BUILD", "false"
|
||||||
|
|
||||||
dimension "variant"
|
dimension "variant"
|
||||||
ndk {
|
ndk {
|
||||||
|
@ -52,12 +54,29 @@ android {
|
||||||
ra32 {
|
ra32 {
|
||||||
applicationIdSuffix '.ra32'
|
applicationIdSuffix '.ra32'
|
||||||
resValue "string", "app_name", "RetroArch (32-bit)"
|
resValue "string", "app_name", "RetroArch (32-bit)"
|
||||||
|
buildConfigField "boolean", "PLAY_STORE_BUILD", "false"
|
||||||
|
|
||||||
dimension "variant"
|
dimension "variant"
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters 'armeabi-v7a', 'x86'
|
abiFilters 'armeabi-v7a', 'x86'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
playStoreNormal {
|
||||||
|
resValue "string", "app_name", "RetroArch"
|
||||||
|
buildConfigField "boolean", "PLAY_STORE_BUILD", "true"
|
||||||
|
|
||||||
|
dimension "variant"
|
||||||
|
}
|
||||||
|
playStoreAarch64 {
|
||||||
|
applicationIdSuffix '.aarch64'
|
||||||
|
resValue "string", "app_name", "RetroArch (AArch64)"
|
||||||
|
buildConfigField "boolean", "PLAY_STORE_BUILD", "true"
|
||||||
|
|
||||||
|
dimension "variant"
|
||||||
|
ndk {
|
||||||
|
abiFilters 'arm64-v8a', 'x86_64'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@ -67,10 +86,10 @@ android {
|
||||||
java.srcDirs = ['src', '../phoenix-common/src']
|
java.srcDirs = ['src', '../phoenix-common/src']
|
||||||
jniLibs.srcDir '../phoenix-common/libs'
|
jniLibs.srcDir '../phoenix-common/libs'
|
||||||
jni.srcDirs = []
|
jni.srcDirs = []
|
||||||
res.srcDirs = ['../phoenix-common/res']
|
res.srcDirs = ['res', '../phoenix-common/res']
|
||||||
}
|
}
|
||||||
aarch64 {
|
aarch64 {
|
||||||
res.srcDirs = ['res64']
|
res.srcDirs = ['res', 'res64']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,3 +123,12 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'com.google.android.play:core:1.8.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
def dynamicFeatures = file("dynamic_features.gradle")
|
||||||
|
if(dynamicFeatures.exists()) {
|
||||||
|
apply from: "dynamic_features.gradle"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This script generates Gradle modules for each Android core,
|
||||||
|
# so that they can be served by Google Play as Dynamic Feature Modules.
|
||||||
|
# Run "./init_modules.sh" to generate modules, or "./init_modules.sh clean" to remove them
|
||||||
|
|
||||||
|
# These paths assume that this script is running inside libretro-super,
|
||||||
|
# and that the compiled Android cores are available while this script is run
|
||||||
|
RECIPES_PATH="../../../../recipes/android"
|
||||||
|
INFO_PATH="../../../../dist/info"
|
||||||
|
CORES_PATH="../../../../dist/android"
|
||||||
|
|
||||||
|
# Get the list of Android cores to generate modules for
|
||||||
|
CORES_LIST=$(cat module_list.txt)
|
||||||
|
|
||||||
|
# The below command would generate a module for every single Android core,
|
||||||
|
# but Dynamic Feature Modules enforces a 50-module limit
|
||||||
|
#CORES_LIST=$(find $RECIPES_PATH -type f ! -name '*.*' -exec cat {} + | awk '{ split($1, test, " "); print test[1] }' | grep "\S")
|
||||||
|
|
||||||
|
# Delete any leftover files from previous script runs
|
||||||
|
rm -rf modules
|
||||||
|
rm -f res/values/core_names.xml
|
||||||
|
rm -f res/values/module_names_*.xml
|
||||||
|
rm -f dynamic_features.gradle
|
||||||
|
rm -f settings.gradle
|
||||||
|
|
||||||
|
if [[ $1 = clean ]] ; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make directory for modules to be stored in
|
||||||
|
mkdir -p modules
|
||||||
|
mkdir -p res/values
|
||||||
|
|
||||||
|
# Begin generating files with necessary metadata
|
||||||
|
# for compiling Dynamic Feature Modules
|
||||||
|
echo "<resources>" >> res/values/core_names.xml
|
||||||
|
echo "android {" >> dynamic_features.gradle
|
||||||
|
echo "dynamicFeatures = [" >> dynamic_features.gradle
|
||||||
|
|
||||||
|
for arch in armeabi-v7a arm64-v8a x86 x86_64
|
||||||
|
do
|
||||||
|
SANITIZED_ARCH_NAME=$(echo $arch | sed "s/-/_/g")
|
||||||
|
echo "<resources>" >> res/values/module_names_$arch.xml
|
||||||
|
echo "<string-array name=\"module_names_$SANITIZED_ARCH_NAME\">" >> res/values/module_names_$arch.xml
|
||||||
|
done
|
||||||
|
|
||||||
|
# Time to generate a module for each core!
|
||||||
|
while IFS= read -r core; do
|
||||||
|
SANITIZED_CORE_NAME=$(echo $core | sed "s/-/_/g")
|
||||||
|
DISPLAY_NAME=$(cat $INFO_PATH/${core}_libretro.info | grep "display_name" | cut -d'"' -f 2)
|
||||||
|
|
||||||
|
echo "Generating module for $core..."
|
||||||
|
|
||||||
|
# Make a copy of the template
|
||||||
|
cp -r module_template modules/$SANITIZED_CORE_NAME
|
||||||
|
|
||||||
|
# Write the name of the core into AndroidManifest.xml
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]
|
||||||
|
then
|
||||||
|
sed -i '' "s/%CORE_NAME%/$SANITIZED_CORE_NAME/g" modules/$SANITIZED_CORE_NAME/AndroidManifest.xml
|
||||||
|
else
|
||||||
|
sed -i "s/%CORE_NAME%/$SANITIZED_CORE_NAME/g" modules/$SANITIZED_CORE_NAME/AndroidManifest.xml
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create a libs directory for each architecture,
|
||||||
|
# and copy the libretro core into each directory
|
||||||
|
for arch in armeabi-v7a arm64-v8a x86 x86_64
|
||||||
|
do
|
||||||
|
mkdir -p modules/$SANITIZED_CORE_NAME/libs/$arch
|
||||||
|
|
||||||
|
if [[ -e $CORES_PATH/$arch/${core}_libretro_android.so ]]
|
||||||
|
then
|
||||||
|
ln -s ../../../../$CORES_PATH/$arch/${core}_libretro_android.so modules/$SANITIZED_CORE_NAME/libs/$arch/lib$core.so
|
||||||
|
else
|
||||||
|
touch modules/$SANITIZED_CORE_NAME/libs/$arch/lib$core.so
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -s "modules/$SANITIZED_CORE_NAME/libs/$arch/lib$core.so" ]]
|
||||||
|
then
|
||||||
|
echo "<item>$core</item>" >> res/values/module_names_$arch.xml
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Write metadata about the module into the corresponding files
|
||||||
|
echo "<string name=\"core_name_$SANITIZED_CORE_NAME\">$DISPLAY_NAME</string>" >> res/values/core_names.xml
|
||||||
|
echo "':modules:$SANITIZED_CORE_NAME'," >> dynamic_features.gradle
|
||||||
|
echo "include ':modules:$SANITIZED_CORE_NAME'" >> settings.gradle
|
||||||
|
done <<< "$CORES_LIST"
|
||||||
|
|
||||||
|
# Finish generating the metadata files
|
||||||
|
echo "</resources>" >> res/values/core_names.xml
|
||||||
|
echo "]" >> dynamic_features.gradle
|
||||||
|
echo "}" >> dynamic_features.gradle
|
||||||
|
|
||||||
|
for arch in armeabi-v7a arm64-v8a x86 x86_64
|
||||||
|
do
|
||||||
|
echo "</string-array>" >> res/values/module_names_$arch.xml
|
||||||
|
echo "</resources>" >> res/values/module_names_$arch.xml
|
||||||
|
done
|
|
@ -0,0 +1,5 @@
|
||||||
|
genesis_plus_gx
|
||||||
|
mesen-s
|
||||||
|
dolphin
|
||||||
|
mupen64plus_next_gles3
|
||||||
|
flycast
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:dist="http://schemas.android.com/apk/distribution"
|
||||||
|
package="com.retroarch.core_modules.%CORE_NAME%">
|
||||||
|
|
||||||
|
<dist:module dist:title="@string/core_name_%CORE_NAME%">
|
||||||
|
<dist:delivery>
|
||||||
|
<dist:on-demand />
|
||||||
|
</dist:delivery>
|
||||||
|
<dist:fusing dist:include="true" />
|
||||||
|
</dist:module>
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:hasCode="false"
|
||||||
|
android:extractNativeLibs="true" />
|
||||||
|
|
||||||
|
</manifest>
|
|
@ -0,0 +1,49 @@
|
||||||
|
apply plugin: 'com.android.dynamic-feature'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 28
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 16
|
||||||
|
targetSdkVersion 28
|
||||||
|
}
|
||||||
|
|
||||||
|
flavorDimensions "variant"
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
normal {
|
||||||
|
dimension "variant"
|
||||||
|
}
|
||||||
|
aarch64 {
|
||||||
|
dimension "variant"
|
||||||
|
ndk {
|
||||||
|
abiFilters 'arm64-v8a', 'x86_64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ra32 {
|
||||||
|
dimension "variant"
|
||||||
|
ndk {
|
||||||
|
abiFilters 'armeabi-v7a', 'x86'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
playStoreNormal {
|
||||||
|
dimension "variant"
|
||||||
|
}
|
||||||
|
playStoreAarch64 {
|
||||||
|
dimension "variant"
|
||||||
|
ndk {
|
||||||
|
abiFilters 'arm64-v8a', 'x86_64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
manifest.srcFile 'AndroidManifest.xml'
|
||||||
|
jniLibs.srcDirs = ['libs']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation rootProject
|
||||||
|
}
|
Loading…
Reference in New Issue