diff --git a/.github/autobuild/android.sh b/.github/autobuild/android.sh index be75d1e72e..dc35decf1c 100755 --- a/.github/autobuild/android.sh +++ b/.github/autobuild/android.sh @@ -26,26 +26,51 @@ set -eu -# Some of the following version pinnings are semi-automatically checked for -# updates. Update .github/workflows/bump-dependencies.yaml when renaming those: -COMMANDLINETOOLS_VERSION=6858069 -ANDROID_NDK_VERSION=r21d -ANDROID_PLATFORM=android-30 -ANDROID_BUILD_TOOLS=30.0.2 -AQTINSTALL_VERSION=3.1.18 -QT_VERSION=5.15.2 - -# Only variables which are really needed by sub-commands are exported. -# Definitions have to stay in a specific order due to dependencies. +## TODO: Decide whether we want to use this action. +## TODO: The patch not only adopts the new action but changes dependencies: +## TODO: - COMMANDLINETOOLS_VERSION from 6858069 to 7.0 +## TODO: - ANDROID_NDK_VERSION from r21d to 25.1.8937393 +## TODO: - ANDROID_PLATFORM from android-30 to android-33 +## TODO: - ANDROID_BUILD_TOOLS from 30.0.2 to 33.0.0 +## TODO: - AQTINSTALL_VERSION from 3.1.18 to 2.1.0 +## TODO: - QT_VERSION from 5.15.2 to 6.3.2 + +# # Some of the following version pinnings are semi-automatically checked for +# # updates. Update .github/workflows/bump-dependencies.yaml when renaming those: +# COMMANDLINETOOLS_VERSION=6858069 +# ANDROID_NDK_VERSION=r21d +# ANDROID_PLATFORM=android-30 +# ANDROID_BUILD_TOOLS=30.0.2 +# AQTINSTALL_VERSION=3.1.18 +# QT_VERSION=5.15.2 + +## From https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2204-Readme.md +# Tools already installed: + # Android Command Line Tools 7.0 + # Android SDK Build-tools 33.0.0 + # Android SDK Platform-Tools 33.0.3 + # Android SDK Platforms android-33 (rev 2) + # Android SDK Tools 26.1.1 + +# Env vars set: + # ANDROID_HOME /usr/local/lib/android/sdk + # ANDROID_NDK /usr/local/lib/android/sdk/ndk/25.1.8937393 + # ANDROID_NDK_HOME /usr/local/lib/android/sdk/ndk/25.1.8937393 + # ANDROID_NDK_LATEST_HOME /usr/local/lib/android/sdk/ndk/25.1.8937393 + # ANDROID_NDK_ROOT /usr/local/lib/android/sdk/ndk/25.1.8937393 + # ANDROID_SDK_ROOT /usr/local/lib/android/sdk + +ANDROID_PLATFORM=android-33 +AQTINSTALL_VERSION=2.1.0 +QT_VERSION=6.3.2 QT_BASEDIR="/opt/Qt" -ANDROID_BASEDIR="/opt/android" BUILD_DIR=build -export ANDROID_SDK_ROOT="${ANDROID_BASEDIR}/android-sdk" -COMMANDLINETOOLS_DIR="${ANDROID_SDK_ROOT}"/cmdline-tools/latest/ -export ANDROID_NDK_ROOT="${ANDROID_BASEDIR}/android-ndk" ANDROID_NDK_HOST="linux-x86_64" -ANDROID_SDKMANAGER="${COMMANDLINETOOLS_DIR}/bin/sdkmanager" -export JAVA_HOME="/usr/lib/jvm/java-8-openjdk-amd64/" +# Github CI image-provided env vars, but explicitly re-assign to placate shellcheck +ANDROID_NDK_ROOT="${ANDROID_NDK_ROOT:?ANDROID_NDK_ROOT should be provided}" +ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:?ANDROID_SDK_ROOT should be provided}" +# Only variables which are really needed by sub-commands are exported. +export JAVA_HOME=${JAVA_HOME_11_X64} export PATH="${PATH}:${ANDROID_SDK_ROOT}/tools" export PATH="${PATH}:${ANDROID_SDK_ROOT}/platform-tools" @@ -58,36 +83,7 @@ setup_ubuntu_dependencies() { export DEBIAN_FRONTEND="noninteractive" sudo apt-get -qq update - sudo apt-get -qq --no-install-recommends -y install build-essential zip unzip bzip2 p7zip-full curl chrpath openjdk-8-jdk-headless -} - -setup_android_sdk() { - mkdir -p "${ANDROID_BASEDIR}" - - if [[ -d "${COMMANDLINETOOLS_DIR}" ]]; then - echo "Using commandlinetools installation from previous run (actions/cache)" - else - mkdir -p "${COMMANDLINETOOLS_DIR}" - curl -s -o downloadfile "https://dl.google.com/android/repository/commandlinetools-linux-${COMMANDLINETOOLS_VERSION}_latest.zip" - unzip -q downloadfile - mv cmdline-tools/* "${COMMANDLINETOOLS_DIR}" - fi - - yes | "${ANDROID_SDKMANAGER}" --licenses - "${ANDROID_SDKMANAGER}" --update - "${ANDROID_SDKMANAGER}" "platforms;${ANDROID_PLATFORM}" - "${ANDROID_SDKMANAGER}" "build-tools;${ANDROID_BUILD_TOOLS}" -} - -setup_android_ndk() { - mkdir -p "${ANDROID_BASEDIR}" - if [[ -d "${ANDROID_NDK_ROOT}" ]]; then - echo "Using NDK installation from previous run (actions/cache)" - else - curl -s -o downloadfile "https://dl.google.com/android/repository/android-ndk-${ANDROID_NDK_VERSION}-linux-x86_64.zip" - unzip -q downloadfile - mv "android-ndk-${ANDROID_NDK_VERSION}" "${ANDROID_NDK_ROOT}" - fi + sudo apt-get -qq --no-install-recommends -y install build-essential zip unzip bzip2 p7zip-full curl chrpath } setup_qt() { @@ -96,54 +92,151 @@ setup_qt() { else echo "Installing Qt..." python3 -m pip install "aqtinstall==${AQTINSTALL_VERSION}" - local qtmultimedia=() - if [[ ! "${QT_VERSION}" =~ 5\..* ]]; then - # From Qt6 onwards, qtmultimedia is a module and cannot be installed - # as an archive anymore. - qtmultimedia=("--modules") - fi - qtmultimedia+=("qtmultimedia") - - python3 -m aqt install-qt --outputdir "${QT_BASEDIR}" linux android "${QT_VERSION}" \ - --archives qtbase qttools qttranslations qtandroidextras \ - "${qtmultimedia[@]}" - # Delete libraries, which we don't use, but which bloat the resulting package and might introduce unwanted dependencies. - find "${QT_BASEDIR}" -name 'libQt5*Quick*.so' -delete - rm -r "${QT_BASEDIR}/${QT_VERSION}/android/qml/" + # icu needs explicit installation + # otherwise: "qmake: error while loading shared libraries: libicui18n.so.56: cannot open shared object file: No such file or directory" + python3 -m aqt install-qt --outputdir "${QT_BASEDIR}" linux desktop "${QT_VERSION}" \ + --archives qtbase qtdeclarative qtsvg qttools icu \ + --modules qtmultimedia + + # - 64bit required for Play Store + python3 -m aqt install-qt --outputdir "${QT_BASEDIR}" linux android "${QT_VERSION}" android_arm64_v8a \ + --archives qtbase qtdeclarative qtsvg qttools \ + --modules qtmultimedia + + # Also install for arm_v7 to build for 32bit devices + python3 -m aqt install-qt --outputdir "${QT_BASEDIR}" linux android "${QT_VERSION}" android_armv7 \ + --archives qtbase qtdeclarative qtsvg qttools \ + --modules qtmultimedia + fi } -build_app_as_apk() { - local QT_DIR="${QT_BASEDIR}/${QT_VERSION}/android" +build_app() { + local ARCH_ABI="${1}" + local MAKE="${ANDROID_NDK_ROOT}/prebuilt/${ANDROID_NDK_HOST}/bin/make" - "${QT_DIR}/bin/qmake" -spec android-clang + echo "${GOOGLE_RELEASE_KEYSTORE}" | base64 --decode > android/android_release.keystore + + echo ">>> Compiling for ${ARCH_ABI} ..." + + # Override ANDROID_ABIS according to build target + # note: seems ANDROID_ABIS can be set here at cmdline, but ANDROID_VERSION_CODE cannot - must be in qmake file + if [ "${ARCH_ABI}" == "android_armv7" ]; then + echo ">>> Running qmake with ANDROID_ABIS=armeabi-v7a ..." + ANDROID_ABIS=armeabi-v7a \ + "${QT_BASEDIR}/${QT_VERSION}/${ARCH_ABI}/bin/qmake" -spec android-clang + elif [ "${ARCH_ABI}" == "android_arm64_v8a" ]; then + echo ">>> Running qmake with ANDROID_ABIS=arm64-v8a ..." + ANDROID_ABIS=arm64-v8a \ + "${QT_BASEDIR}/${QT_VERSION}/${ARCH_ABI}/bin/qmake" -spec android-clang + elif [ "${ARCH_ABI}" == "android_x86" ]; then + echo ">>> Running qmake with ANDROID_ABIS=arm64-v8a ..." + ANDROID_ABIS=x86 \ + "${QT_BASEDIR}/${QT_VERSION}/${ARCH_ABI}/bin/qmake" -spec android-clang + elif [ "${ARCH_ABI}" == "android_x86_64" ]; then + echo ">>> Running qmake with ANDROID_ABIS=arm64-v8a ..." + ANDROID_ABIS=x86_64 \ + "${QT_BASEDIR}/${QT_VERSION}/${ARCH_ABI}/bin/qmake" -spec android-clang + fi "${MAKE}" -j "$(nproc)" - "${MAKE}" INSTALL_ROOT="${BUILD_DIR}" -f Makefile install - "${QT_DIR}"/bin/androiddeployqt --input android-Jamulus-deployment-settings.json --output "${BUILD_DIR}" \ - --android-platform "${ANDROID_PLATFORM}" --jdk "${JAVA_HOME}" --gradle + "${MAKE}" INSTALL_ROOT="${BUILD_DIR}_${ARCH_ABI}" -f Makefile install +} + +build_make_clean() { + echo ">>> Doing make clean ..." + local MAKE="${ANDROID_NDK_ROOT}/prebuilt/${ANDROID_NDK_HOST}/bin/make" + "${MAKE}" clean + rm -f Makefile +} + +build_aab() { + local ARCH_ABI="${1}" + + if [ "${ARCH_ABI}" == "android_armv7" ]; then + TARGET_ABI=armeabi-v7a + elif [ "${ARCH_ABI}" == "android_arm64_v8a" ]; then + TARGET_ABI=arm64-v8a + elif [ "${ARCH_ABI}" == "android_x86" ]; then + TARGET_ABI=x86 + elif [ "${ARCH_ABI}" == "android_x86_64" ]; then + TARGET_ABI=x86_64 + fi + echo ">>> Building .aab file for ${TARGET_ABI}...." + + ANDROID_ABIS=${TARGET_ABI} ${QT_BASEDIR}/${QT_VERSION}/gcc_64/bin/androiddeployqt --input android-Jamulus-deployment-settings.json \ + --verbose \ + --output "${BUILD_DIR}_${ARCH_ABI}" \ + --aab \ + --release \ + --sign android/android_release.keystore jamulus \ + --storepass "${GOOGLE_KEYSTORE_PASS}" \ + --android-platform "${ANDROID_PLATFORM}" \ + --jdk "${JAVA_HOME}" \ + --gradle } pass_artifact_to_job() { - mkdir deploy - local artifact="jamulus_${JAMULUS_BUILD_VERSION}_android.apk" - echo "Moving ${BUILD_DIR}/build/outputs/apk/debug/build-debug.apk to deploy/${artifact}" - mv "./${BUILD_DIR}/build/outputs/apk/debug/build-debug.apk" "./deploy/${artifact}" - echo "artifact_1=${artifact}" >> "$GITHUB_OUTPUT" + local ARCH_ABI="${1}" + echo ">>> Deploying .aab file for ${ARCH_ABI}...." + + if [ "${ARCH_ABI}" == "android_armv7" ]; then + NUM="1" + BUILDNAME="arm" + elif [ "${ARCH_ABI}" == "android_arm64_v8a" ]; then + NUM="2" + BUILDNAME="arm64" + elif [ "${ARCH_ABI}" == "android_x86" ]; then + NUM="3" + BUILDNAME="x86" + elif [ "${ARCH_ABI}" == "android_x86_64" ]; then + NUM="4" + BUILDNAME="x86_64" + fi + + #mkdir deploy + #local artifact="jamulus_${JAMULUS_BUILD_VERSION}_android.apk" + mkdir -p deploy + local artifact="Jamulus_${JAMULUS_BUILD_VERSION}_android_${BUILDNAME}.aab" + # debug to check for filenames + ls -alR "${BUILD_DIR}_${ARCH_ABI}/build/outputs/bundle/release/" + ls -al "${BUILD_DIR}_${ARCH_ABI}/build/outputs/bundle/release/build_${ARCH_ABI}-release.aab" + + #echo "Moving ${BUILD_DIR}/build/outputs/apk/debug/build-debug.apk to deploy/${artifact}" + #mv "./${BUILD_DIR}/build/outputs/apk/debug/build-debug.apk" "./deploy/${artifact}" + echo ">>> Moving ${BUILD_DIR}_${ARCH_ABI}/build/outputs/bundle/release/build_${ARCH_ABI}-release.aab to deploy/${artifact}" + mv "./${BUILD_DIR}_${ARCH_ABI}/build/outputs/bundle/release/build_${ARCH_ABI}-release.aab" "./deploy/${artifact}" + echo ">>> Moved .aab file to deploy/${artifact}" + echo ">>> Artifact number is: ${NUM}" + echo ">>> Setting output as such: name=artifact_${NUM}::${artifact}" + #echo "artifact_1=${artifact}" >> "$GITHUB_OUTPUT" + echo "::set-output name=artifact_${NUM}::${artifact}" } case "${1:-}" in setup) setup_ubuntu_dependencies - setup_android_ndk - setup_android_sdk setup_qt ;; build) - build_app_as_apk + # Build all targets in sequence + build_app "android_armv7" + build_aab "android_armv7" + build_make_clean + build_app "android_arm64_v8a" + build_aab "android_arm64_v8a" + build_make_clean + build_app "android_x86" + build_aab "android_x86" + build_make_clean + build_app "android_x86_64" + build_aab "android_x86_64" ;; get-artifacts) - pass_artifact_to_job + pass_artifact_to_job "android_armv7" + pass_artifact_to_job "android_arm64_v8a" + pass_artifact_to_job "android_x86" + pass_artifact_to_job "android_x86_64" ;; *) echo "Unknown stage '${1:-}'" diff --git a/.github/workflows/autobuild.yml b/.github/workflows/autobuild.yml index 00fc3d4f2b..8a70e7d048 100644 --- a/.github/workflows/autobuild.yml +++ b/.github/workflows/autobuild.yml @@ -377,6 +377,9 @@ jobs: NOTARIZATION_PASSWORD: ${{ secrets.NOTARIZATION_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} MACOS_CA_PUBLICKEY: ${{ secrets.MACOS_CA_PUBKEY }} + GOOGLE_RELEASE_KEYSTORE: ${{ secrets.GOOGLE_KEYSTORE }} + GOOGLE_KEYSTORE_PASS: ${{ secrets.GOOGLE_KEYSTORE_PASS }} + - name: Post-Build for ${{ matrix.config.config_name }} id: get-artifacts run: ${{ matrix.config.base_command }} get-artifacts @@ -427,6 +430,25 @@ jobs: JAMULUS_BUILD_VERSION: ${{ needs.create_release.outputs.build_version }} ARTIFACT_PATH: deploy/${{ steps.get-artifacts.outputs.artifact_1 }} + ## RELEASE PROCEDURE FOR: + ## - Android Play Store - aab + ## Requirement: Service Account JSON setup: + ## - Google Play Console -> Setup -> API Access -> Create/Link Google Cloud Project + ## - Google Cloud console -> IAM & Admin -> Service Accounts -> Create (Wizard). Then create JSON key and export/save + - name: Publish all Android ABI builds to Play Store + if: >- + needs.create_release.outputs.publish_to_release == 'true' && + matrix.config.target_os == 'android' + id: publish_android + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJsonPlainText: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }} + packageName: io.jamulus.jamulus + releaseFiles: deploy/Jamulus*.aab + releaseName: ${{ needs.create_release.outputs.build_version }} + track: beta + status: draft + - name: Deploy Artifact 1 to Release if: needs.create_release.outputs.publish_to_release == 'true' id: upload-release-asset1 diff --git a/Jamulus.pro b/Jamulus.pro index b9228b018c..a75b539b43 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -238,18 +238,53 @@ win32 { LIBS += -framework AVFoundation \ -framework AudioToolbox } else:android { - ANDROID_ABIS = armeabi-v7a arm64-v8a x86 x86_64 + # By default build for all the ABIs + # ANDROID_ABIS = armeabi-v7a arm64-v8a x86 x86_64 + + # get ANDROID_ABIS from environment - passed directly to qmake + ANDROID_ABIS = $$getenv(ANDROID_ABIS) + + # Optional: if ANDROID_ABIS is passed as env var to qmake, will override this + # !defined(ANDROID_ABIS, var):ANDROID_ABIS = arm64-v8a + + # Experiments show likely better results with Android 10+ devices + ANDROID_MIN_SDK_VERSION = 29 + ANDROID_TARGET_SDK_VERSION = 32 ANDROID_VERSION_NAME = $$VERSION - ANDROID_VERSION_CODE = $$system(git log --oneline | wc -l) + + ## For local Dev use on Windows/WSA: + equals(QMAKE_HOST.os, Windows) { + ANDROID_ABIS = x86_64 + ANDROID_VERSION_CODE = 1234 # dummy int value + } else { + # date-based unique integer value for Play Store submission + !defined(ANDROID_VERSION_CODE, var):ANDROID_VERSION_CODE = $$system(date +%s | cut -c 2-) + } + + # make separate version codes for each abi build otherwise Play Store rejects + contains (ANDROID_ABIS, armeabi-v7a) { + ANDROID_VERSION_CODE = $$num_add($$ANDROID_VERSION_CODE, 1) + message("Setting for armeabi-v7a: ANDROID_VERSION_CODE=$${ANDROID_VERSION_CODE}") + } + contains (ANDROID_ABIS, x86) { + ANDROID_VERSION_CODE = $$num_add($$ANDROID_VERSION_CODE, 2) + message("Setting for x86: ANDROID_VERSION_CODE=$${ANDROID_VERSION_CODE}") + } + contains (ANDROID_ABIS, x86_64) { + ANDROID_VERSION_CODE = $$num_add($$ANDROID_VERSION_CODE, 3) + message("Setting for x86_64: ANDROID_VERSION_CODE=$${ANDROID_VERSION_CODE}") + } + message("Setting ANDROID_VERSION_NAME=$${ANDROID_VERSION_NAME} ANDROID_VERSION_CODE=$${ANDROID_VERSION_CODE}") # liboboe requires C++17 for std::timed_mutex CONFIG += c++17 - QT += androidextras + # For device recording permissions + QT += core-private # for Qt6 # enabled only for debugging on android devices - DEFINES += ANDROIDDEBUG + #DEFINES += ANDROIDDEBUG target.path = /tmp/your_executable # path on device INSTALLS += target diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 7725864d27..1dcb23cdfc 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,6 +1,5 @@ - @@ -17,8 +16,8 @@ - - + + @@ -27,24 +26,6 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/main.cpp b/src/main.cpp index 42d17a8dea..be9b3e543f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -40,7 +40,7 @@ #endif #include "util.h" #ifdef ANDROID -# include +# include #endif #if defined( Q_OS_MACOS ) # include "mac/activity.h" @@ -825,14 +825,14 @@ int main ( int argc, char** argv ) #endif #ifdef ANDROID - // special Android coded needed for record audio permission handling - auto result = QtAndroid::checkPermission ( QString ( "android.permission.RECORD_AUDIO" ) ); + // special Android code needed for record audio permission handling + auto recaudio_check = QtAndroidPrivate::checkPermission ( QString ( "android.permission.RECORD_AUDIO" ) ); - if ( result == QtAndroid::PermissionResult::Denied ) + if ( recaudio_check.result() == QtAndroidPrivate::PermissionResult::Denied ) { - QtAndroid::PermissionResultMap resultHash = QtAndroid::requestPermissionsSync ( QStringList ( { "android.permission.RECORD_AUDIO" } ) ); + auto recaudio_reqPermRes = QtAndroidPrivate::requestPermission ( "android.permission.RECORD_AUDIO" ); - if ( resultHash["android.permission.RECORD_AUDIO"] == QtAndroid::PermissionResult::Denied ) + if ( recaudio_reqPermRes.result() == QtAndroidPrivate::PermissionResult::Denied ) { return 0; }