diff --git a/Package.swift b/Package.swift index 933da08..540c202 100644 --- a/Package.swift +++ b/Package.swift @@ -12,9 +12,11 @@ let package = Package( dependencies: [ .package(url: "https://source.skip.tools/skip.git", from: "1.2.34"), .package(url: "https://source.skip.tools/skip-foundation.git", from: "1.3.1"), - .package(url: "https://source.skip.tools/swift-jni.git", "0.0.0"..<"2.0.0"), - .package(url: "https://source.skip.tools/skip-bridge.git", "0.0.0"..<"2.0.0"), - .package(url: "https://source.skip.tools/swift-android-native.git", from: "1.4.1") + //.package(url: "https://source.skip.tools/swift-jni.git", "0.0.0"..<"2.0.0"), + .package(url: "https://source.skip.tools/swift-jni.git", branch: "swift-java-jni-cutover"), // ### REMOVEME + //.package(url: "https://source.skip.tools/skip-bridge.git", "0.0.0"..<"2.0.0"), + .package(url: "https://source.skip.tools/skip-bridge.git", branch: "swift-java-jni-cutover"), + .package(url: "https://github.com/swift-android-sdk/swift-android-native.git", from: "2.0.1") ], targets: [ .target(name: "SkipAndroidBridge", dependencies: [ @@ -31,6 +33,7 @@ let package = Package( .target(name: "SkipAndroidBridgeSamples", dependencies: [ "SkipAndroidBridge", + .product(name: "AndroidContext", package: "swift-android-native", condition: .when(platforms: [.android])), ], resources: [.process("Resources")], plugins: [.plugin(name: "skipstone", package: "skip")]), .testTarget(name: "SkipAndroidBridgeSamplesTests", dependencies: [ "SkipAndroidBridgeSamples", @@ -38,3 +41,22 @@ let package = Package( ], resources: [.process("Resources")], plugins: [.plugin(name: "skipstone", package: "skip")]), ] ) + +if let dependencyRoot = Context.environment["SKIP_DEPENDENCY_ROOT"] { + package.dependencies = package.dependencies.map { dep in + switch dep.kind { + case .sourceControl(_, let location, _): + guard let baseName = location.split(separator: "/").last?.split(separator: ".").first else { + return dep + } + // Remap skip* and swift-jni (the SWIFT_JAVA_JNI_CORE substrate; a direct dep here) to local; + // leave swift-android-native on its declared fork URL. + guard baseName.hasPrefix("skip") || baseName == "swift-jni" else { + return dep + } + return Package.Dependency.package(path: dependencyRoot + "/" + baseName) + default: + return dep + } + } +} diff --git a/Sources/SkipAndroidBridge/AndroidBridgeBootstrap.swift b/Sources/SkipAndroidBridge/AndroidBridgeBootstrap.swift index fcba764..e404e9c 100644 --- a/Sources/SkipAndroidBridge/AndroidBridgeBootstrap.swift +++ b/Sources/SkipAndroidBridge/AndroidBridgeBootstrap.swift @@ -87,10 +87,10 @@ public class AndroidBridgeBootstrap { try AssetURLProtocol.register() logger.debug("initAndroidBridge: bootstrapTimezone") try bootstrapTimezone() - logger.debug("initAndroidBridge: setupCACerts") - try AndroidBootstrap.setupCACerts() - logger.debug("initAndroidBridge: AndroidLooper.setupMainLooper") - let _ = AndroidLooper.setupMainLooper() + // CA-certificate bootstrap for URLSession HTTPS is now provided by the Swift SDK for Android, + // so the former AndroidBootstrap.setupCACerts() call is no longer needed. + logger.debug("initAndroidBridge: AndroidMainActor.setupMainLooper") + let _ = AndroidMainActor.setupMainLooper() logger.debug("initAndroidBridge: done") #endif logger.debug("AndroidBridgeBootstrap.initAndroidBridge done in \(Date.now.timeIntervalSince(start)) applicationSupportDirectory=\(URL.applicationSupportDirectory.path)") @@ -140,3 +140,33 @@ extension URL { } #endif + +#if os(Android) +/// Minimal OSLog-`Logger`-compatible shim for Android. +/// +/// The skiptools `AndroidLogging` used to vend an OSLog-style `Logger`; the swift-android-sdk +/// `AndroidLogging` vends `AndroidLogger` instead. This re-provides the small `Logger` surface this +/// module relies on, forwarding messages to logcat via `AndroidLogger` (`__android_log_write`). +/// `AndroidLogger`/`LogTag`/`LogPriority` are available via the module's `@_exported import AndroidLogging`. +public struct Logger: Sendable { + private let tag: LogTag + + public init(subsystem: String, category: String) { + self.tag = LogTag(rawValue: category) + } + + public func trace(_ message: String) { emit(message, .verbose) } + public func debug(_ message: String) { emit(message, .debug) } + public func info(_ message: String) { emit(message, .info) } + public func notice(_ message: String) { emit(message, .info) } + public func warning(_ message: String) { emit(message, .warning) } + public func error(_ message: String) { emit(message, .error) } + public func critical(_ message: String) { emit(message, .error) } + public func fault(_ message: String) { emit(message, .error) } + public func log(_ message: String) { emit(message, .info) } + + private func emit(_ message: String, _ priority: LogPriority) { + try? AndroidLogger(tag: tag, priority: priority).log(message) + } +} +#endif diff --git a/Sources/SkipAndroidBridge/AssetURLProtocol.swift b/Sources/SkipAndroidBridge/AssetURLProtocol.swift index c7898fd..f25de5c 100644 --- a/Sources/SkipAndroidBridge/AssetURLProtocol.swift +++ b/Sources/SkipAndroidBridge/AssetURLProtocol.swift @@ -3,7 +3,7 @@ #if os(Android) import Foundation import FoundationNetworking -import AndroidAssetManager +import AndroidFileManager import AndroidLogging @preconcurrency import SwiftJNI @@ -15,7 +15,7 @@ public class AssetURLProtocol: URLProtocol { public static let scheme = "asset" nonisolated(unsafe) private static var registered = false - nonisolated(unsafe) private static var assetManager: AndroidAssetManager? = nil + nonisolated(unsafe) private static var assetManager: AssetManager? = nil public static func register() throws { if registered { return } @@ -32,7 +32,7 @@ public class AssetURLProtocol: URLProtocol { throw AndroidAssetError(errorDescription: "no value for ProcessInfo.processInfo.dynamicAndroidContext.toJavaObject") } let am = JNI.jni.withEnv { intf, env in - AndroidAssetManager(env: env, peer: jobj) + AssetManager.fromJava(jobj, environment: env) } Self.assetManager = am Self.registered = true @@ -91,4 +91,17 @@ public struct AndroidAssetError : LocalizedError { } } +private extension AssetManager { + /// Reads the entire contents of the named asset into `Data`, or returns nil if it cannot be + /// opened or read. Replaces the former `AndroidAssetManager.load(from:)` convenience that the + /// swift-android-sdk `AndroidFileManager.AssetManager` does not provide. + func load(from path: String) -> Data? { + guard let asset = try? open(path) else { return nil } + return try? asset.readAll { (buffer: UnsafeRawBufferPointer) -> Data in + guard let base = buffer.baseAddress else { return Data() } + return Data(bytes: base, count: buffer.count) + } + } +} + #endif diff --git a/Sources/SkipAndroidBridgeSamples/SkipAndroidBridgeSamples.swift b/Sources/SkipAndroidBridgeSamples/SkipAndroidBridgeSamples.swift index 2c0979c..50d4e9a 100644 --- a/Sources/SkipAndroidBridgeSamples/SkipAndroidBridgeSamples.swift +++ b/Sources/SkipAndroidBridgeSamples/SkipAndroidBridgeSamples.swift @@ -7,6 +7,9 @@ import SwiftJNI #if canImport(AndroidNative) import AndroidNative #endif +#if canImport(AndroidContext) +import AndroidContext +#endif public let swiftStringConstant = "s"