diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index ea175a30..2c24b0d0 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -1,21 +1,52 @@ name: Swift -on: [push] -jobs: +on: + push: + branches: [master] + workflow_dispatch: + pull_request: +jobs: android: name: Android strategy: fail-fast: false matrix: - swift: ['6.2.3', 'nightly-6.3'] - arch: ['aarch64', 'x86_64', 'armv7'] - sdk: ['28', '29', '31', '33'] - runs-on: macos-15 - timeout-minutes: 30 + # os: ['ubuntu-24.04', 'macos-15-intel'] + os: ['ubuntu-24.04'] + android-sdk: ['nightly-6.3'] + android-api: ['28', '29', '31', '33'] + android-ndk: ['r27d'] + runs-on: ${{ matrix.os }} + timeout-minutes: 90 steps: - - uses: actions/checkout@v4 - - name: "Build Swift Package for Android" + - uses: actions/checkout@v6 + - uses: skiptools/actions/setup-skip@v1 + with: + gradle-version: 'none' + install-swift-android-sdk: true + swift-android-sdk-version: ${{ matrix.android-sdk }} + swift-android-ndk-version: ${{ matrix.android-ndk }} + + - name: "Build Swift Package for Android (aarch64)" + run: | + ANDROID_SDK_VERSION=${{ matrix.android-api }} skip android build --arch aarch64 --android-api-level ${{ matrix.android-api }} + + - name: "Build Swift Package for Android (armv7)" run: | - brew install skiptools/skip/skip || (brew update && brew install skiptools/skip/skip) - skip android sdk install --version ${{ matrix.swift }} - ANDROID_NDK_ROOT="" ANDROID_SDK_VERSION=${{ matrix.sdk }} skip android build --arch ${{ matrix.arch }} --android-api-level ${{ matrix.sdk }} + ANDROID_SDK_VERSION=${{ matrix.android-api }} skip android build --arch armv7 --android-api-level ${{ matrix.android-api }} + + - name: "Build Swift Package for Android (x86_64)" + run: | + ANDROID_SDK_VERSION=${{ matrix.android-api }} skip android build --arch x86_64 --android-api-level ${{ matrix.android-api }} + + - name: "Test Swift Package on Android" + # can only lookup the current JavaVirtualMachine on API >= 31 + # https://github.com/swiftlang/swift-java/issues/419 + if: ${{ matrix.android-api >= 31 }} + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.android-api }} + arch: x86_64 + script: | + ANDROID_SDK_VERSION=${{ matrix.android-api }} skip android test --android-api-level ${{ matrix.android-api }} --apk --verbose + diff --git a/Package.swift b/Package.swift index c580d798..83b6e289 100644 --- a/Package.swift +++ b/Package.swift @@ -288,6 +288,21 @@ var package = Package( sdkVersionDefine, ] ), + .testTarget( + name: "AndroidAppTests", + dependencies: [ + "AndroidJava", + "AndroidApp", + .product( + name: "AndroidContext", + package: "swift-android-native" + ), + .product( + name: "SwiftJava", + package: "swift-java" + ), + ] + ), .target( name: "AndroidContent", dependencies: [ diff --git a/Tests/AndroidAppTests/AndroidAppTests.swift b/Tests/AndroidAppTests/AndroidAppTests.swift new file mode 100644 index 00000000..2c465463 --- /dev/null +++ b/Tests/AndroidAppTests/AndroidAppTests.swift @@ -0,0 +1,48 @@ +import Testing +import SwiftJavaJNICore +import SwiftJava +import AndroidOS +import AndroidApp +import AndroidContext +@preconcurrency import AndroidContent + +extension AndroidApp.Application { + /// The shared `android.app.Application` + public static func currentApplication() throws -> Application { + let jvm = try JavaVirtualMachine.shared() + let env = try jvm.environment() + + // get the low-level application context wrapper from swift-android-native + let lowLevelContext = try AndroidContext.application + + // wrap it in the @JavaClass Context + return Application(javaHolder: JavaObjectHolder(object: lowLevelContext.pointer, environment: env)) + } +} + +extension AndroidContent.Context { + /// The shared `android.content.Context` for the current application + public static func applicationContext() throws -> Context { + // simply cast the Application to a Context + try Application.currentApplication().as(AndroidContent.Context.self)! + } +} + +@Test func testAndroidAppContext() throws { + let ctx = try Context.applicationContext() + + let name = ctx.getPackageName() + #expect(name == "org.swift.test.swiftandroid", "test harness named by skip android test") + + let info: AndroidContent.ApplicationInfo = ctx.getApplicationInfo() + #expect(info.processName == "org.swift.test.swiftandroid") + #expect(info.minSdkVersion >= 28) + + let looper: AndroidOS.Looper = ctx.getMainLooper() + #expect(looper.isCurrentThread() == false, "tests should not be running on main thread") + + let activityManagerOb = try #require(ctx.getSystemService(ctx.javaClass.ACTIVITY_SERVICE)) + let activityManager: ActivityManager = ActivityManager(javaHolder: activityManagerOb.javaHolder) + + #expect(activityManager.javaClass.isUserAMonkey() == false, "aren't we all monkeys, though?") +}