diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b37574a..47e458b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,21 +7,33 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions/setup-java@v3 with: distribution: "temurin" java-version: "17" - - name: "Setup Gradle" - uses: gradle/gradle-build-action@v2 - - name: "Grant execution permission" run: chmod +x ./gradlew - name: "Build APKs" - run: ./gradlew build + run: | + # Retry the Gradle build up to 3 times to mitigate transient cache/service errors + ATTEMPT=0 + until [ $ATTEMPT -ge 3 ] + do + ./gradlew build --no-daemon --stacktrace && break + ATTEMPT=$((ATTEMPT+1)) + echo "Gradle build failed, retrying ($ATTEMPT/3)..." + sleep 5 + done + if [ $ATTEMPT -ge 3 ]; then + echo "Gradle build failed after 3 attempts" + exit 1 + fi - name: "Setup directories" run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 901be79..c6f7aa3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,77 +1,99 @@ name: Build Release on: - workflow_dispatch: - inputs: - versionName: - required: true - description: This releases version name - default: "1.0.0" - versionCode: - required: true - description: This releases version code - default: "1000" + workflow_dispatch: + inputs: + versionName: + required: true + description: This releases version name + default: "1.0.0" + versionCode: + required: true + description: This releases version code + default: "1000" permissions: - contents: write + contents: write jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@master - - - name: Setup JDK 17 - uses: actions/setup-java@v2 - with: - java-version: 17 - distribution: 'temurin' - cache: 'gradle' + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: Setup Android SDK - uses: android-actions/setup-android@v2.0.10 + - name: Setup JDK 17 + uses: actions/setup-java@v3 + with: + distribution: "temurin" + java-version: 17 - - name: Set Version - uses: chkfung/android-version-actions@v1.1 - with: - gradlePath: app/build.gradle.kts - versionCode: ${{github.event.inputs.versionCode}} - versionName: ${{github.event.inputs.versionName}} - - - name: Bump Version - continue-on-error: true - run: | - git config --local user.email "actions@github.com" - git config --local user.name "GitHub Actions" - git add app/build.gradle.kts - if ! git diff --quiet app/build.gradle.kts; then - git commit -m "chore: Bump version to ${{github.event.inputs.versionName}} (${{github.event.inputs.versionCode}})" - git push - else - echo "No changes to version file, skipping commit" - fi - - - name: Build Signed APK - run: | - echo "${{ secrets.keystore }}" | base64 -d > $GITHUB_WORKSPACE/signing-key.jks - chmod +x ./gradlew - ./gradlew packageReleaseUniversalApk \ - -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/signing-key.jks \ - -Pandroid.injected.signing.store.password=${{ secrets.keystore_password }} \ - -Pandroid.injected.signing.key.alias=${{ secrets.key_alias }} \ - -Pandroid.injected.signing.key.password=${{ secrets.key_password }} - - - name: Release - run: | - mv app/build/outputs/apk_from_bundle/release/app-release-universal.apk ./app-release.apk - git config --local user.email "actions@github.com" - git config --local user.name "GitHub Actions" - tag="${{ github.event.inputs.versionCode }}" - git tag "$tag" - git push origin "$tag" - gh release create "$tag" \ - --title "${{ github.event.inputs.versionName }}" \ - --generate-notes \ - ./*.apk - env: - GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + - name: Setup Android SDK + uses: android-actions/setup-android@v2.0.10 + + - name: Set Version + uses: chkfung/android-version-actions@v1.1 + with: + gradlePath: app/build.gradle.kts + versionCode: ${{github.event.inputs.versionCode}} + versionName: ${{github.event.inputs.versionName}} + + - name: Bump Version + continue-on-error: true + run: | + git config --local user.email "actions@github.com" + git config --local user.name "GitHub Actions" + git add app/build.gradle.kts + if ! git diff --quiet app/build.gradle.kts; then + git commit -m "chore: Bump version to ${{github.event.inputs.versionName}} (${{github.event.inputs.versionCode}})" + git push + else + echo "No changes to version file, skipping commit" + fi + + - name: Build Signed APK + run: | + # Ensure keystore secret is present + if [ -z "${{ secrets.keystore }}" ]; then + echo "Keystore secret missing, aborting" + exit 1 + fi + + echo "${{ secrets.keystore }}" | base64 -d > $GITHUB_WORKSPACE/signing-key.jks + chmod +x ./gradlew + + # Retry gradle up to 3 times to mitigate transient network/cache service issues + ATTEMPT=0 + until [ $ATTEMPT -ge 3 ] + do + ./gradlew packageReleaseUniversalApk \ + -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/signing-key.jks \ + -Pandroid.injected.signing.store.password=${{ secrets.keystore_password }} \ + -Pandroid.injected.signing.key.alias=${{ secrets.key_alias }} \ + -Pandroid.injected.signing.key.password=${{ secrets.key_password }} --no-daemon --stacktrace && break + + ATTEMPT=$((ATTEMPT+1)) + echo "Gradle build failed, retrying ($ATTEMPT/3)..." + sleep 5 + done + + if [ $ATTEMPT -ge 3 ]; then + echo "Gradle build failed after 3 attempts" + exit 1 + fi + + - name: Release + run: | + mv app/build/outputs/apk_from_bundle/release/app-release-universal.apk ./app-release.apk + git config --local user.email "actions@github.com" + git config --local user.name "GitHub Actions" + tag="${{ github.event.inputs.versionCode }}" + git tag "$tag" + git push origin "$tag" + gh release create "$tag" \ + --title "${{ github.event.inputs.versionName }}" \ + --generate-notes \ + ./*.apk + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/app/src/main/kotlin/io/github/revenge/xposed/modules/UpdaterModule.kt b/app/src/main/kotlin/io/github/revenge/xposed/modules/UpdaterModule.kt index 1def83a..d199f09 100644 --- a/app/src/main/kotlin/io/github/revenge/xposed/modules/UpdaterModule.kt +++ b/app/src/main/kotlin/io/github/revenge/xposed/modules/UpdaterModule.kt @@ -98,11 +98,10 @@ class UpdaterModule : Module() { // Retries don't need timeout if (activity != null) { - timeout { - requestTimeoutMillis = - if (!bundle.exists()) TIMEOUT - else TIMEOUT_CACHED - } + val timeoutMs = + if (!bundle.exists()) TIMEOUT + else TIMEOUT_CACHED + timeout { requestTimeoutMillis = timeoutMs } } } diff --git a/app/src/main/kotlin/io/github/revenge/xposed/modules/bridge/BridgeModule.kt b/app/src/main/kotlin/io/github/revenge/xposed/modules/bridge/BridgeModule.kt index eac6d27..59f4763 100644 --- a/app/src/main/kotlin/io/github/revenge/xposed/modules/bridge/BridgeModule.kt +++ b/app/src/main/kotlin/io/github/revenge/xposed/modules/bridge/BridgeModule.kt @@ -49,7 +49,7 @@ typealias BridgeMethodArgs = ArrayList */ class BridgeModule : Module() { private lateinit var readableMapGetString: Method - private lateinit var readableMapToHashMap: Method + private lateinit var readableMapToHashMapMethod: Method private lateinit var argumentsMakeNative: Method companion object { @@ -126,43 +126,46 @@ class BridgeModule : Module() { } } - override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = - with(packageParam) { - val arguments = classLoader.loadClass("com.facebook.react.bridge.Arguments") - val readableMap = classLoader.loadClass("com.facebook.react.bridge.ReadableMap") - val promise = classLoader.loadClass("com.facebook.react.bridge.Promise") + override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) { + with(packageParam) { + val arguments = classLoader.loadClass("com.facebook.react.bridge.Arguments") + val readableMap = classLoader.loadClass("com.facebook.react.bridge.ReadableMap") + val promise = classLoader.loadClass("com.facebook.react.bridge.Promise") - val promiseResolve = promise.method("resolve", Object::class.java) - argumentsMakeNative = arguments.method("makeNativeObject", Object::class.java) - readableMapGetString = readableMap.method("getString", String::class.java) - readableMapToHashMap = readableMap.method("toHashMap") + val promiseResolve = promise.method("resolve", Object::class.java) + argumentsMakeNative = arguments.method("makeNativeObject", Object::class.java) + readableMapGetString = readableMap.method("getString", String::class.java) + readableMapToHashMapMethod = readableMap.method("toHashMap") - classLoader.loadClass("com.horcrux.svg.RNSVGRenderableManager").hookMethod( - "getBBox", - Double::class.javaObjectType, - readableMap - ) { - before { - callBridgeMethod(readableMapToHashMap(args[1]!!))?.let { - result = it.toNativeObject() - } + classLoader.loadClass("com.horcrux.svg.RNSVGRenderableManager").hookMethod( + "getBBox", + Double::class.javaObjectType, + readableMap + ) { + before { + callBridgeMethod(readableMapToHashMapConverter(args[1]!!))?.let { + result = it.toNativeObject() } } - - classLoader.loadClass("com.facebook.react.modules.blob.FileReaderModule") - .hookMethod("readAsDataURL", readableMap, promise) { - before { - val (readableMap, promise) = args - callBridgeMethod(readableMapToHashMap(readableMap!!))?.let { - promiseResolve.invoke(promise, it.toNativeObject()) - result = null - } - } - } - - return@with } + classLoader.loadClass("com.facebook.react.modules.blob.FileReaderModule").hookMethod( + "readAsDataURL", + readableMap, + promise + ) { + before { + // Avoid shadowing the outer `readableMap` and `promise` variables + val (readableMapArg, promiseArg) = args + callBridgeMethod(readableMapToHashMapConverter(readableMapArg!!))?.let { + promiseResolve.invoke(promiseArg, it.toNativeObject()) + result = null + } + } + } + } + } + private fun Any?.toNativeObject(): Any? = argumentsMakeNative.invoke( null, @@ -172,18 +175,21 @@ class BridgeModule : Module() { } ) - private fun readableMapToHashMap(map: Any): HashMap { - @Suppress("UNCHECKED_CAST") return readableMapToHashMap.invoke(map) as HashMap + private fun readableMapToHashMapConverter(map: Any): HashMap { + @Suppress("UNCHECKED_CAST") + return readableMapToHashMapMethod.invoke(map) as HashMap } - private fun callBridgeMethod(hashMap: HashMap): Map? = - try { - val (method, args) = getBridgeCallData(hashMap) ?: return null - val ret = method(args).toNativeObject() - mapOf("result" to ret) - } catch (e: Throwable) { - mapOf("error" to e.toString()) - } + private fun callBridgeMethod(hashMap: HashMap): Map? { + try { + val pair = getBridgeCallData(hashMap) ?: return null + val (method, args) = pair + val ret = method(args).toNativeObject() + return mapOf("result" to ret) + } catch (e: Throwable) { + return mapOf("error" to e.toString()) + } + } private fun getBridgeCallData( hashMap: HashMap