Update Android project
Some checks failed
Build / build-appimage (push) Blocked by required conditions
Build / build-android (push) Blocked by required conditions
Build / build-windows (push) Blocked by required conditions
Build / download-system-deps (push) Successful in 47s
Build / build-gentoo (push) Successful in 1m8s
Build / get-source-code (push) Has been cancelled

This commit is contained in:
Zachary Hall 2024-10-21 13:28:38 -07:00
parent fda208e643
commit 1f8c7e46bb
13 changed files with 294 additions and 197 deletions

Binary file not shown.

View file

@ -9,7 +9,7 @@ jobs:
- name: Download and cache system dependencies - name: Download and cache system dependencies
uses: https://complecwaft.com/catmeow/cache-apt-pkgs-action@latest uses: https://complecwaft.com/catmeow/cache-apt-pkgs-action@latest
with: with:
packages: zstd cmake build-essential python3 python3-pip python3-venv wget gcc-mingw-w64 g++-mingw-w64 ninja-build binutils-mingw-w64-x86_64 packages: zstd cmake build-essential python3 python3-pip python3-venv wget gcc-mingw-w64 g++-mingw-w64 ninja-build binutils-mingw-w64-x86-64
version: 1.0 version: 1.0
get-source-code: get-source-code:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -137,7 +137,7 @@ jobs:
- name: Download and cache system dependencies - name: Download and cache system dependencies
uses: https://complecwaft.com/catmeow/cache-apt-pkgs-action@latest uses: https://complecwaft.com/catmeow/cache-apt-pkgs-action@latest
with: with:
packages: zstd cmake build-essential gcc-mingw-w64 g++-mingw-w64 ninja-build binutils-mingw-w64-x86_64 packages: zstd cmake build-essential gcc-mingw-w64 g++-mingw-w64 ninja-build binutils-mingw-w64-x86-64
version: 1.0 version: 1.0
- name: Download repository code - name: Download repository code
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
@ -154,7 +154,7 @@ jobs:
- name: Build protoc - name: Build protoc
run: ./build-protoc.sh run: ./build-protoc.sh
- name: Build with CMake - name: Build with CMake
run: mkdir -p build-windows && cd build-windows && cmake .. -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-gcc -G Ninja -DDISABLE_GTK_UI=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_LIBFMT=ON -DUSE_PORTALS=OFF -DENABLE_DBUS=OFF -DBUILD_STATIC=ON -DDOWNLOAD_AUDIO_CODECS_DEPENDENCY=ON -DSDL_MIXER_X_SHARED=OFF -DSDL_MIXER_X_STATIC=ON -DUSE_OGG_VORBIS_STB=ON -DUSE_OPUS=OFF -DUSE_MODPLUG=OFF -DUSE_GME=OFF -DUSE_WAVPACK=OFF -DUSE_XMP=OFF -DUSE_MIDI_EDMIDI=OFF -DUSE_SYSTEM_SDL2=ON -DBUILD_PROTOC=OFF -DBUILD_SDL=ON -DBUILD_SDL_IMAGE=ON -DBUILD_JSONCPP=ON -DBUILD_SOUNDTOUCH=ON -DBUILD_PROTOBUF=ON -DDISABLE_IMGUI_UI=OFF -D BUILD_FMT=ON && env PATH="/usr/x86_64-w64-mingw32/bin:$PATH" ninja && cd .. run: mkdir -p build-windows && cd build-windows && cmake .. -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-gcc -G Ninja -DDISABLE_GTK_UI=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_LIBFMT=ON -DUSE_PORTALS=OFF -DENABLE_DBUS=OFF -DBUILD_STATIC=ON -DDOWNLOAD_AUDIO_CODECS_DEPENDENCY=ON -DSDL_MIXER_X_SHARED=OFF -DSDL_MIXER_X_STATIC=ON -DUSE_OGG_VORBIS_STB=ON -DUSE_OPUS=OFF -DUSE_MODPLUG=OFF -DUSE_GME=OFF -DUSE_WAVPACK=OFF -DUSE_XMP=OFF -DUSE_MIDI_EDMIDI=OFF -DUSE_SYSTEM_SDL2=ON -DBUILD_PROTOC=OFF -DBUILD_SDL=ON -DBUILD_SDL_IMAGE=ON -DBUILD_JSONCPP=ON -DBUILD_SOUNDTOUCH=ON -DBUILD_PROTOBUF=ON -DDISABLE_IMGUI_UI=OFF -D BUILD_FMT=ON && ninja && cd ..
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:

28
sdl-android-project/.gitmodules vendored Normal file
View file

@ -0,0 +1,28 @@
[submodule "../backends/ui/imgui/imgui"]
commit = 99109c0b3b052cffa154a9295440f68868a39f74
[submodule "../subprojects/SDL"]
commit = 79ec168f3c1e2fe27335cb8886439f7ef676fb49
[submodule "../subprojects/SDL-Mixer-X"]
commit = 22aed1f6bcfa6912a34d3241edf3bd90498a6bc2
[submodule "../subprojects/SDL_image"]
commit = abcf63aa71b4e3ac32120fa9870a6500ddcdcc89
[submodule "../subprojects/fmt"]
commit = 0379bf3a5d52d8542aec1874677c9df5ff9ba5f9
[submodule "../subprojects/googletest"]
commit = 6dae7eb4a5c3a169f3e298392bff4680224aa94a
[submodule "../subprojects/grpc"]
commit = e821cdc231bda9ee93139a6daab6311dd8953832
[submodule "../subprojects/jsoncpp"]
commit = 8214f717e7c7d361f002b6c3d1b1086ddd096315
[submodule "../subprojects/libintl-lite"]
commit = 44035a0c3fe20ef28f071b35b9f5a653fbfe5e6d
[submodule "../subprojects/oboe"]
commit = 86165b8249bc22b9ef70b69e20323244b6f08d88
[submodule "../subprojects/protobuf"]
commit = 63def39e881afa496502d9c410f4ea948e59490d
[submodule "../subprojects/protobuf-c"]
commit = 8c201f6e47a53feaab773922a743091eb6c8972a
[submodule "../subprojects/soundtouch"]
commit = e83424d5928ab8513d2d082779c275765dee31b9
[submodule "../subprojects/vgmstream"]
commit = 1d836a3693541811d4a5089cf8c68d6acd4b1eeb

View file

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>sdl-android-project</name>
<comment>Project sdl-android-project created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
<filteredResources>
<filter>
<id>1729361463965</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

View file

@ -1,13 +0,0 @@
arguments=--init-script /home/catmeow/.local/share/zed/extensions/work/java/jdt-language-server-1.40.0/configuration/org.eclipse.osgi/57/0/.cp/gradle/init/init.gradle
auto.sync=false
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
gradle.user.home=
java.home=/opt/openjdk-bin-21.0.4_p7
jvm.arguments=
offline.mode=false
override.workspace.settings=true
show.console.view=true
show.executions.view=true

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

View file

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>app</name>
<comment>Project app created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
<filteredResources>
<filter>
<id>1729361463958</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

View file

@ -0,0 +1,4 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
org.eclipse.jdt.core.compiler.compliance=21
org.eclipse.jdt.core.compiler.source=21

View file

@ -6,7 +6,6 @@ if (buildAsApplication) {
else { else {
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
} }
android { android {
buildFeatures { buildFeatures {
prefab true prefab true
@ -19,8 +18,26 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 30 minSdkVersion 30
targetSdkVersion 34 targetSdkVersion 34
versionCode 1 def revListStdout = new ByteArrayOutputStream();
versionName "1.0" exec {
executable "git"
commandLine "git", "rev-list", "--count", "HEAD"
standardOutput revListStdout
}
def revParseStdout = new ByteArrayOutputStream();
def versionNameData = ""
exec {
commandLine "git", "rev-parse", "--abbref-rev", "HEAD"
standardOutput revParseStdout
}
versionNameData = revParseStdout.toString() + "-"
revParseStdout = new ByteArrayOutputStream()
exec {
commandLine "git", "rev-parse", "--short", "HEAD"
standardOutput revParseStdout
}
versionCode revListStdout.toString() as Integer
versionName versionNameData + revParseStdout.toString()
externalNativeBuild { externalNativeBuild {
cmake { cmake {
arguments "-DUSE_GLES=ON", "-DUSE_PORTALS=OFF", "-DDOWNLOAD_AUDIO_CODECS_DEPENDENCY=ON", "-DENABLE_DBUS=OFF", "-DBUILD_SDL=ON", "-DBUILD_SDL_IMAGE=ON", "-DDISABLE_GTK_UI=ON", "-DDISABLE_IMGUI_UI=OFF" arguments "-DUSE_GLES=ON", "-DUSE_PORTALS=OFF", "-DDOWNLOAD_AUDIO_CODECS_DEPENDENCY=ON", "-DENABLE_DBUS=OFF", "-DBUILD_SDL=ON", "-DBUILD_SDL_IMAGE=ON", "-DDISABLE_GTK_UI=ON", "-DDISABLE_IMGUI_UI=OFF"
@ -66,6 +83,7 @@ android {
} }
dependencies { dependencies {
implementation "androidx.core:core:1.13.1"
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation group: 'com.getkeepsafe.relinker', name: 'relinker', version: '1.4.5' implementation group: 'com.getkeepsafe.relinker', name: 'relinker', version: '1.4.5'
} }

View file

@ -1,10 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Replace com.test.game with the identifier of your game below, e.g.
com.gamemaker.game
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:installLocation="auto" android:installLocation="auto"
package="com.complecwaft.looper"> >
<queries> <queries>
<intent> <intent>
<action android:name="android.intent.action.OPEN_DOCUMENT" /> <action android:name="android.intent.action.OPEN_DOCUMENT" />
@ -53,13 +50,15 @@
<!-- Allow access to the vibrator --> <!-- Allow access to the vibrator -->
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- if you want to capture audio, uncomment this. --> <!-- if you want to capture audio, uncomment this. -->
<!-- <uses-permission android:name="android.permission.RECORD_AUDIO" /> --> <!-- <uses-permission android:name="android.permission.RECORD_AUDIO" /> -->
<!-- Create a Java class extending SDLActivity and place it in a <!-- Create a Java class extending SDLActivity and place it in a
directory under app/src/main/java matching the package, e.g. app/src/main/java/com/gamemaker/game/MyGame.java directory under app/src/main/java matching the package, e.g. app/src/main/java/com/gamemaker/game/MyGame.java
then replace "SDLActivity" with the name of your class (e.g. "MyGame") then replace "SDLActivity" with the name of your class (e.g. "MyGame")
in the XML below. in the XML below.
@ -70,11 +69,12 @@
android:allowBackup="true" android:allowBackup="true"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:hardwareAccelerated="true" > android:hardwareAccelerated="true" >
<service android:name="com.complecwaft.looper.PlaybackService" android:foregroundServiceType="mediaPlayback" android:exported="false" />
<!-- Example of setting SDL hints from AndroidManifest.xml: <!-- Example of setting SDL hints from AndroidManifest.xml:
<meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/> <meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/>
--> -->
<activity android:name="MainActivity" <activity android:name="MainActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:alwaysRetainTaskState="true" android:alwaysRetainTaskState="true"

View file

@ -1,114 +1,184 @@
package com.complecwaft.looper; package com.complecwaft.looper;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.util.Log;
import org.libsdl.app.SDLActivity;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Objects;
import android.media.AudioManager;
public class MainActivity extends SDLActivity
{
public static MainActivity mSingleton;
@Override
protected void onCreate(Bundle savedInstanceState) {
mSingleton = this;
super.onCreate(savedInstanceState);
}
private static Intent openDocumentIntent = null;
public static String GetUserDir() {
return System.getProperty("user.home");
}
private static final int PICK_FILE = 0x33;
public static void OpenFilePicker(Object extraInitialUri) {
openDocumentIntent = new Intent(Intent.ACTION_GET_CONTENT);
openDocumentIntent.addCategory(Intent.CATEGORY_OPENABLE);
openDocumentIntent.setType("*/*");
if (extraInitialUri instanceof String) {
openDocumentIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, (String)extraInitialUri);
}
if (openDocumentIntent.resolveActivity(mSingleton.getPackageManager()) != null) {
mSingleton.startActivityForResult(Intent.createChooser(openDocumentIntent, "Open a file..."), PICK_FILE);
} else {
Log.d("Looper", "Unable to resolve Intent.ACTION_OPEN_DOCUMENT {}");
}
} import org.libsdl.app.SDLActivity;
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (requestCode == PICK_FILE && resultCode == RESULT_OK) {
Uri uri = null;
if (resultData != null) {
uri = resultData.getData();
InputStream input = null;
loading = true;
try {
input = getApplication().getContentResolver().openInputStream(uri);
} catch (FileNotFoundException ex) {
ex.printStackTrace();
PickedFile = null;
loading = false; import android.Manifest;
return; import android.app.NotificationChannel;
} import android.app.NotificationManager;
File file = new File(getCacheDir(), uri.getLastPathSegment()); import android.content.Context;
try { import android.content.Intent;
file.getParentFile().mkdirs(); import android.content.pm.PackageManager;
file.createNewFile(); import android.net.Uri;
FileOutputStream os = null; import android.os.Build;
os = new FileOutputStream(file); import android.os.Bundle;
int len = 0; import android.provider.DocumentsContract;
int pos = 0; import android.util.Log;
byte[] buf = new byte[1024*4]; import androidx.core.app.NotificationCompat.Builder;
import androidx.core.app.ServiceCompat;
while ((len = input.read(buf)) >= 0) { public class MainActivity extends SDLActivity {
os.write(buf, 0, len);
pos += len; public static MainActivity mSingleton;
} private static Intent mediaPlaybackService;
} catch (IOException e) { private static Intent requestPermissionLauncher;
e.printStackTrace(); @Override
PickedFile = null; protected void onCreate(Bundle savedInstanceState) {
loading = false; if (
return; this.getBaseContext().checkSelfPermission(
} Manifest.permission.POST_NOTIFICATIONS
loading = false; ) ==
PickedFile = file.getAbsolutePath(); PackageManager.PERMISSION_GRANTED
} else { ) {
PickedFile = null; this.onRequestPermissionsResult(PERMISSION_REQUEST, new String[] {Manifest.permission.POST_NOTIFICATIONS}, new int[] {PackageManager.PERMISSION_GRANTED});
} } else {
} // You can directly ask for the permission.
} // The registered ActivityResultCallback gets the result of this request.
private static boolean loading; this.requestPermissions(
public static boolean IsLoading() { new String[] {Manifest.permission.POST_NOTIFICATIONS},
return loading; PERMISSION_REQUEST
} );
private static String PickedFile = null; }
public static String GetPickedFile() { mSingleton = this;
if (openDocumentIntent == null) { super.onCreate(savedInstanceState);
return ""; }
} else if (PickedFile == null) { @Override
return ""; public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
} else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return PickedFile; int importance = NotificationManager.IMPORTANCE_DEFAULT;
} NotificationChannel channel = new NotificationChannel(
} "com.complecwaft.looper.MediaPlayback",
public static void ClearSelected() { "Media Playback",
PickedFile = null; importance
} );
channel.setDescription(
"Notification for keeping playback in the foreground"
);
NotificationManager notificationManager = getSystemService(
NotificationManager.class
);
notificationManager.createNotificationChannel(channel);
}
mediaPlaybackService = new Intent(this, PlaybackService.class);
startService(mediaPlaybackService);
}
@Override
protected void onDestroy() {
stopService(mediaPlaybackService);
super.onDestroy();
mSingleton = null;
}
private static Intent openDocumentIntent = null;
public static String GetUserDir() {
return System.getProperty("user.home");
}
private static final int PICK_FILE = 0x33;
private static final int PERMISSION_REQUEST = 0x34;
public static void OpenFilePicker(Object extraInitialUri) {
openDocumentIntent = new Intent(Intent.ACTION_GET_CONTENT);
openDocumentIntent.addCategory(Intent.CATEGORY_OPENABLE);
openDocumentIntent.setType("*/*");
if (extraInitialUri instanceof String) {
openDocumentIntent.putExtra(
DocumentsContract.EXTRA_INITIAL_URI,
(String) extraInitialUri
);
}
if (
openDocumentIntent.resolveActivity(
mSingleton.getPackageManager()
) !=
null
) {
mSingleton.startActivityForResult(
Intent.createChooser(openDocumentIntent, "Open a file..."),
PICK_FILE
);
} else {
Log.d("Looper", "Unable to resolve Intent.ACTION_OPEN_DOCUMENT {}");
}
}
@Override
public void onActivityResult(
int requestCode,
int resultCode,
Intent resultData
) {
if (requestCode == PICK_FILE && resultCode == RESULT_OK) {
Uri uri = null;
if (resultData != null) {
uri = resultData.getData();
InputStream input = null;
loading = true;
try {
input = getApplication()
.getContentResolver()
.openInputStream(uri);
} catch (FileNotFoundException ex) {
ex.printStackTrace();
PickedFile = null;
loading = false;
return;
}
File file = new File(getCacheDir(), uri.getLastPathSegment());
try {
file.getParentFile().mkdirs();
file.createNewFile();
FileOutputStream os = null;
os = new FileOutputStream(file);
int len = 0;
int pos = 0;
byte[] buf = new byte[1024 * 4];
while ((len = input.read(buf)) >= 0) {
os.write(buf, 0, len);
pos += len;
}
} catch (IOException e) {
e.printStackTrace();
PickedFile = null;
loading = false;
return;
}
loading = false;
PickedFile = file.getAbsolutePath();
} else {
PickedFile = null;
}
}
}
private static boolean loading;
public static boolean IsLoading() {
return loading;
}
private static String PickedFile = null;
public static String GetPickedFile() {
if (openDocumentIntent == null) {
return "";
} else if (PickedFile == null) {
return "";
} else {
return PickedFile;
}
}
public static void ClearSelected() {
PickedFile = null;
}
} }

View file

@ -0,0 +1,57 @@
package com.complecwaft.looper;
import android.app.ForegroundServiceStartNotAllowedException;
import android.app.Notification;
import android.app.Service;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.Binder;
import android.os.IBinder;
import androidx.core.app.NotificationCompat;
import androidx.core.app.ServiceCompat;
public class PlaybackService extends Service {
private void startForeground() {
try {
Notification notification = new NotificationCompat.Builder(
this,
"com.complecwaft.looper.MediaPlayback"
).build();
int type = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
type = ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
}
ServiceCompat.startForeground(
this,
100, // Cannot be 0
notification,
type
);
} catch (Exception e) {
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
e instanceof ForegroundServiceStartNotAllowedException
) {
// App not in a valid state to start foreground service
// (e.g started from bg)
}
}
}
@Override
public void onCreate() {
startForeground();
}
@Override
public void onDestroy() {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE);
}
@Override
public IBinder onBind(Intent arg0) {
return new Binder();
}
}

View file

@ -16,3 +16,4 @@ ndk_version=27.2.12479018
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true # org.gradle.parallel=true
android.useAndroidX=true