From 077acc68802e5946bd55185d9789f90833dd9b64 Mon Sep 17 00:00:00 2001 From: Sergii Pylypenko Date: Mon, 7 Jun 2021 00:20:09 +0300 Subject: [PATCH] SDL: support for install-time asset pack for Android app bundle --- .gitignore | 1 + changeAppSettings.sh | 14 +++- copyAssets.sh | 48 ++++++------- project/app/build-template.gradle | 3 + project/assetpack/build.gradle | 8 +++ project/assetpack/src/main/assets | 1 + project/java/DataDownloader.java | 67 ++++++++++++++++++- project/java/MainActivity.java | 1 + project/java/Settings.java | 4 ++ project/jni/application/openarena/.gitignore | 1 + .../openarena/AndroidAppSettings.cfg | 12 +++- project/jni/application/openarena/engine | 2 +- project/settings.gradle | 1 + readme.txt | 21 +++++- 14 files changed, 153 insertions(+), 31 deletions(-) create mode 100644 project/assetpack/build.gradle create mode 120000 project/assetpack/src/main/assets diff --git a/.gitignore b/.gitignore index cdf70d78e..257b3912a 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ /project/app/build.gradle /project/app/.gradle /project/app/build +/project/assetpack/build diff --git a/changeAppSettings.sh b/changeAppSettings.sh index b873cda4c..7a390fd93 100755 --- a/changeAppSettings.sh +++ b/changeAppSettings.sh @@ -166,7 +166,7 @@ echo >> AndroidAppSettings.cfg echo "# Application user-visible version name (string)" >> AndroidAppSettings.cfg echo AppVersionName=\"$AppVersionName\" >> AndroidAppSettings.cfg echo >> AndroidAppSettings.cfg -echo "# Specify path to download application data in zip archive in the form 'Description|URL|MirrorURL^Description2|URL2|MirrorURL2^...'" >> AndroidAppSettings.cfg +echo "# Specify path to download application data in zip archive in the form \"Description|URL|MirrorURL^Description2|URL2|MirrorURL2^...\"" >> AndroidAppSettings.cfg echo "# If you'll start Description with '!' symbol it will be enabled by default, '!!' will also hide the entry from the menu, so it cannot be disabled" >> AndroidAppSettings.cfg echo "# If the URL in in the form ':dir/file.dat:http://URL/' it will be downloaded as binary BLOB to the application dir and not unzipped" >> AndroidAppSettings.cfg echo "# If the URL does not contain 'http://' or 'https://', it is treated as file from 'project/jni/application/src/AndroidData' dir -" >> AndroidAppSettings.cfg @@ -174,6 +174,14 @@ echo "# these files are put inside .apk package by the build system" >> AndroidA echo "# You can specify Google Play expansion files in the form 'obb:main.12345' or 'obb:patch.12345' where 12345 is the app version for the obb file" >> AndroidAppSettings.cfg echo "# You can mount expansion files created with jobb tool if you put 'mnt:main.12345' or 'mnt:patch.12345'" >> AndroidAppSettings.cfg echo "# The mount directory will be returned by calling getenv(\"ANDROID_OBB_MOUNT_DIR\")" >> AndroidAppSettings.cfg +echo "# Android app bundles do not support .obb files, they use asset packs instead." >> AndroidAppSettings.cfg +echo "# This app project includes one pre-configured install-time asset pack." >> AndroidAppSettings.cfg +echo "# To put your data into asset pack, copy it to the directory AndroidData/assetpack" >> AndroidAppSettings.cfg +echo "# and run changeAppSettings.sh. The asset pack zip archive will be returned by" >> AndroidAppSettings.cfg +echo "# getenv(\"ANDROID_ASSET_PACK_PATH\"), this call will return NULL if the asset pack is not installed." >> AndroidAppSettings.cfg +echo "# You can put \"assetpack\" keyword to AppDataDownloadUrl, the code will check" >> AndroidAppSettings.cfg +echo "# if the asset pack is installed and will not download the data from other URLs." >> AndroidAppSettings.cfg +echo "# You can extract files from the asset pack the same way you extract files from the app assets." >> AndroidAppSettings.cfg echo "# You can use .zip.xz archives for better compression, but you need to add 'lzma' to CompiledLibraries" >> AndroidAppSettings.cfg echo "# Generate .zip.xz files like this: zip -0 -r data.zip your-data/* ; xz -8 data.zip" >> AndroidAppSettings.cfg echo AppDataDownloadUrl=\"$AppDataDownloadUrl\" >> AndroidAppSettings.cfg @@ -1116,6 +1124,10 @@ else } fi +if [ -z "`ls project/assetpack/src/main/assets/ 2>/dev/null`" ] ; then + $SEDI "/==ASSETPACK==/ d" project/app/build.gradle +fi + if [ -e project/jni/application/src/project.patch ]; then patch -p1 --dry-run -f -R < project/jni/application/src/project.patch > /dev/null 2>&1 || patch -p1 --no-backup-if-mismatch < project/jni/application/src/project.patch || exit 1 ; fi rm -f project/lib diff --git a/copyAssets.sh b/copyAssets.sh index 3159cae7b..56c78343a 100755 --- a/copyAssets.sh +++ b/copyAssets.sh @@ -3,36 +3,38 @@ ARCHES="arm64-v8a armeabi-v7a x86 x86_64" if [ "$1" = "pack-binaries" -o "$1" = "pack-binaries-bundle" ]; then -[ -e jni/application/src/AndroidData/lib ] || exit 0 -[ -e jni/application/src/AndroidData/binaries*.zip ] && { - echo "Error: binaries.zip no longer supported in Android 10" - echo "Copy your executable binaries to AndroidData/lib/arm64-v8a" - echo "Then execute them using \$LIBDIR or getenv(\"LIBDIR\")" + [ -e jni/application/src/AndroidData/lib ] || exit 0 + [ -e jni/application/src/AndroidData/binaries*.zip ] && { + echo "Error: binaries.zip no longer supported in Android 10" + echo "Copy your executable binaries to AndroidData/lib/arm64-v8a" + echo "Then execute them using \$LIBDIR or getenv(\"LIBDIR\")" + exit 0 + } + APK="`pwd`/$2" + echo "Copying binaries to .apk file" + cd jni/application/src/AndroidData/ || exit 1 + if [ "$1" = "pack-binaries-bundle" ]; then + rm -rf base + mkdir -p base + cp -L -r lib base/ + zip -r -D "$APK" base || exit 1 + rm -rf base + else + zip -r -D "$APK" lib || exit 1 + fi + cd ../../../../ exit 0 -} -APK="`pwd`/$2" -echo "Copying binaries to .apk file" -cd jni/application/src/AndroidData/ || exit 1 -if [ "$1" = "pack-binaries-bundle" ]; then - rm -rf base - mkdir -p base - mv lib base/ - zip -r -D "$APK" base || exit 1 - mv base/lib ./ - rm -rf base -else - zip -r -D "$APK" lib || exit 1 -fi -cd ../../../../ -exit 0 fi echo "Copying app data files from project/jni/application/src/AndroidData to project/assets" mkdir -p project/assets rm -f -r project/assets/* if [ -d "project/jni/application/src/AndroidData" ] ; then - cp -L -r project/jni/application/src/AndroidData/* project/assets/ - rm -rf project/assets/lib + for F in project/jni/application/src/AndroidData/*; do + [ "$F" = "project/jni/application/src/AndroidData/lib" ] && continue + [ "$F" = "project/jni/application/src/AndroidData/assetpack" ] && continue + cp -L -r "$F" project/assets/ + done fi exit 0 diff --git a/project/app/build-template.gradle b/project/app/build-template.gradle index 8e8f87800..15e41d7b2 100644 --- a/project/app/build-template.gradle +++ b/project/app/build-template.gradle @@ -27,10 +27,13 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } + assetPacks = [":assetpack"] // ==ASSETPACK== + dependencies { compile 'com.google.android.gms:play-services-games:21.0.0' } // ==GOOGLEPLAYGAMESERVICES== dependencies { compile 'com.google.android.gms:play-services-drive:17.0.0' } // ==GOOGLEPLAYGAMESERVICES== } dependencies { implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.play:core:1.10.0' } diff --git a/project/assetpack/build.gradle b/project/assetpack/build.gradle new file mode 100644 index 000000000..a887c1741 --- /dev/null +++ b/project/assetpack/build.gradle @@ -0,0 +1,8 @@ +apply plugin: 'com.android.asset-pack' + +assetPack { + packName = "assetpack" + dynamicDelivery { + deliveryType = "install-time" + } +} diff --git a/project/assetpack/src/main/assets b/project/assetpack/src/main/assets new file mode 120000 index 000000000..f587c6d4d --- /dev/null +++ b/project/assetpack/src/main/assets @@ -0,0 +1 @@ +../../../jni/application/src/AndroidData/assetpack \ No newline at end of file diff --git a/project/java/DataDownloader.java b/project/java/DataDownloader.java index d34c1abbb..42d60e82e 100644 --- a/project/java/DataDownloader.java +++ b/project/java/DataDownloader.java @@ -59,7 +59,11 @@ import android.content.pm.PackageManager; import android.os.storage.StorageManager; import android.os.storage.OnObbStateChangeListener; - +//import com.google.android.play.core.assetpacks.AssetPackManagerFactory; +//import com.google.android.play.core.assetpacks.AssetPackManager; +import android.content.res.AssetManager; +import android.content.pm.PackageManager; +import android.content.pm.ApplicationInfo; class CountingInputStream extends BufferedInputStream { @@ -323,7 +327,66 @@ class DataDownloader extends Thread partialDownloadLen = partialDownload.length(); } Status.setText( downloadCount + "/" + downloadTotal + ": " + res.getString(R.string.connecting_to, url) ); - if( url.indexOf("obb:") == 0 || url.indexOf("mnt:") == 0 ) // APK expansion file provided by Google Play + if( url.equals("assetpack") ) + { + Log.i("SDL", "Checking for asset pack"); + /* + try + { + AssetPackManager assetPackManager = AssetPackManagerFactory.getInstance(Parent.getApplicationContext()); + Log.i("SDL", "Parent.getApplicationContext(): " + Parent.getApplicationContext() + " assetPackManager " + assetPackManager); + if( assetPackManager != null ) + { + Log.i("SDL", "assetPackManager.getPackLocation(): " + assetPackManager.getPackLocation("assetpack")); + if( assetPackManager.getPackLocation("assetpack") != null ) + { + String assetPackPath = assetPackManager.getPackLocation("assetpack").assetsPath(); + Parent.assetPackPath = assetPackPath; + if( assetPackPath != null ) + { + Log.i("SDL", "Asset pack is installed at: " + assetPackPath); + return true; + } + } + } + } + catch( Exception e ) + { + Log.i("SDL", "Asset pack exception: " + e); + } + */ + try + { + if( android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP ) + { + ApplicationInfo info = Parent.getPackageManager().getApplicationInfo(Parent.getPackageName(), 0); + if( info.splitSourceDirs != null ) + { + for( String apk: info.splitSourceDirs ) + { + Log.i("SDL", "Package apk: " + apk); + if( apk.endsWith("assetpack.apk") ) + { + Parent.assetPackPath = apk; + } + } + } + } + } + catch( Exception e ) + { + Log.i("SDL", "Asset pack exception: " + e); + } + if( Parent.assetPackPath != null ) + { + Log.i("SDL", "Found asset pack: " + Parent.assetPackPath); + return true; + } + Log.i("SDL", "Asset pack is not installed"); + downloadUrlIndex++; + continue; + } + else if( url.indexOf("obb:") == 0 || url.indexOf("mnt:") == 0 ) // APK expansion file provided by Google Play { boolean tmpMountObb = ( url.indexOf("mnt:") == 0 ); url = getObbFilePath(url); diff --git a/project/java/MainActivity.java b/project/java/MainActivity.java index 41e93a81b..f1fa1eda9 100644 --- a/project/java/MainActivity.java +++ b/project/java/MainActivity.java @@ -1501,6 +1501,7 @@ public class MainActivity extends Activity public boolean writeExternalStoragePermissionDialogAnswered = false; public String ObbMountPath = null; + public String assetPackPath = null; // Not saved to the config file } // *** HONEYCOMB / ICS FIX FOR FULLSCREEN MODE, by lmak *** diff --git a/project/java/Settings.java b/project/java/Settings.java index 6510ee86c..da34dca90 100644 --- a/project/java/Settings.java +++ b/project/java/Settings.java @@ -690,6 +690,10 @@ public class Settings { nativeSetEnv( "ANDROID_OBB_MOUNT_DIR", p.ObbMountPath ); } + if( p.assetPackPath != null ) + { + nativeSetEnv( "ANDROID_ASSET_PACK_PATH", p.assetPackPath ); + } try { nativeSetEnv( "ANDROID_APP_NAME", p.getString(p.getApplicationInfo().labelRes) ); } catch (Exception eeeeee) {} diff --git a/project/jni/application/openarena/.gitignore b/project/jni/application/openarena/.gitignore index 1e7fefa40..8d48f67fb 100644 --- a/project/jni/application/openarena/.gitignore +++ b/project/jni/application/openarena/.gitignore @@ -1,2 +1,3 @@ /AndroidData/*.pk3 +/AndroidData/assetpack /*.so diff --git a/project/jni/application/openarena/AndroidAppSettings.cfg b/project/jni/application/openarena/AndroidAppSettings.cfg index 430a5c010..fdee269c8 100644 --- a/project/jni/application/openarena/AndroidAppSettings.cfg +++ b/project/jni/application/openarena/AndroidAppSettings.cfg @@ -7,10 +7,10 @@ AppName="OpenArena" AppFullName=ws.openarena.sdl # Application version code (integer) -AppVersionCode=08841 +AppVersionCode=08844 # Application user-visible version name (string) -AppVersionName="0.8.8.41" +AppVersionName="0.8.8.44" # Specify path to download application data in zip archive in the form 'Description|URL|MirrorURL^Description2|URL2|MirrorURL2^...' # If you'll start Description with '!' symbol it will be enabled by default, '!!' will also hide the entry from the menu, so it cannot be disabled @@ -20,6 +20,14 @@ AppVersionName="0.8.8.41" # You can specify Google Play expansion files in the form 'obb:main.12345' or 'obb:patch.12345' where 12345 is the app version for the obb file # You can mount expansion files created with jobb tool if you put 'mnt:main.12345' or 'mnt:patch.12345' # The mount directory will be returned by calling getenv("ANDROID_OBB_MOUNT_DIR") +# Android app bundles do not support .obb files, they use asset packs instead. +# This app project includes one pre-configured install-time asset pack. +# To put your data into asset pack, copy it to the directory AndroidData/assetpack +# and run changeAppSettings.sh. The asset pack zip archive will be returned by +# getenv("ANDROID_ASSET_PACK_PATH"), this call will return NULL if the asset pack is not installed. +# You can put "assetpack" keyword to AppDataDownloadUrl, the code will check +# if the asset pack is installed and will not download the data from other URLs. +# You can extract files from the asset pack the same way you extract files from the app assets. # You can use .zip.xz archives for better compression, but you need to add 'lzma' to CompiledLibraries # Generate .zip.xz files like this: zip -0 -r data.zip your-data/* ; xz -8 data.zip AppDataDownloadUrl="!Game data|mnt:main.8839|obb:main.8828|https://github.com/pelya/openarena-engine/releases/download/0.8.8.39/data-0.8.8.zip|https://sourceforge.net/projects/libsdl-android/files/OpenArena/data-0.8.8.28.zip/download^!Game logic|:baseoa/pak7-android.pk3:pak7-android.pk3" diff --git a/project/jni/application/openarena/engine b/project/jni/application/openarena/engine index 521e8a320..79ccc3bdd 160000 --- a/project/jni/application/openarena/engine +++ b/project/jni/application/openarena/engine @@ -1 +1 @@ -Subproject commit 521e8a3207fabaa5e219dd6bdcdcc7402abea92d +Subproject commit 79ccc3bdd3011c4281aac875e60f6553d345dcd3 diff --git a/project/settings.gradle b/project/settings.gradle index e7b4def49..d81aff2c6 100644 --- a/project/settings.gradle +++ b/project/settings.gradle @@ -1 +1,2 @@ include ':app' +include ':assetpack' diff --git a/readme.txt b/readme.txt index 535337a90..e4f05f733 100644 --- a/readme.txt +++ b/readme.txt @@ -134,11 +134,17 @@ env CXXFLAGS='-frtti -fexceptions' ../setEnvironment.sh ./configure Application data may be bundled with app itself, or downloaded from the internet on the first run - if you want to put app data inside .apk file - create a .zip archive and put it into the directory project/jni/application/src/AndroidData (create it if it doesn't exist), then run ChangeAppSettings.sh -and specify the file name there. If the data files are more than 10 Mb it's a good idea to put them -on public HTTP server - you may specify URL in ChangeAppSettings.sh, also you may specify several files. +and specify the file name there. If the data files are more than 150 Mb then it's a good idea to put them +on public HTTP server - you may specify URL in AppDataDownloadUrl in AndroidAppSettings.cfg, also you may specify several files. If you'll release new version of data files you should change download URL or data file name and update your app as well - the app will re-download the data if URL does not match the saved URL from previous download. +AppDataDownloadUrl can have several URLs in the form "Description|URL|MirrorURL^Description2|URL2|MirrorURL2^..." +If you'll start Description with '!' symbol it will be enabled by default, '!!' will also hide the entry from the menu, so it cannot be disabled. +If the URL in in the form ':dir/file.dat:http://URL/' it will be downloaded as binary BLOB to the application dir and not unzipped. +If the URL does not contain 'http://' or 'https://', it is treated as file from 'project/jni/application/src/AndroidData' dir - +these files are put inside .apk package by the build system. + You can also create .obb file using jobb tool, and attach it to your app on Play Store: jobb -pn xyz.yourserver.yourapp -pv 123 -d ./data -o main.123.xyz.yourserver.yourapp.obb @@ -160,6 +166,17 @@ AppDataDownloadUrl="!!Game data|mnt:main.123|https://yourserver.xyz/gamedata.zip You also need to specify ReadObbFile=y inside AndroidAppSettings.cfg. +Android app bundles do not support .obb files, they use asset packs instead. +This app project includes one pre-configured install-time asset pack. +To put your data into asset pack, copy it to the directory AndroidData/assetpack +and run changeAppSettings.sh. The asset pack zip archive will be returned by +getenv("ANDROID_ASSET_PACK_PATH"), this call will return NULL if the asset pack is not installed. +You can put "assetpack" keyword to AppDataDownloadUrl, the code will check +if the asset pack is installed and will not download the data from other URLs. +You can extract files from the asset pack the same way you extract files from the app assets. + +AppDataDownloadUrl="!!Game data|assetpack|https://yourserver.xyz/gamedata.zip" + All devices have different screen resolutions, you may toggle automatic screen resizing in ChangeAppSettings.sh and draw to virtual 640x480 screen - it will be HW accelerated and will not impact performance. Automatic screen resizing does not work in SDL 1.3/2.0.