-
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathAndroidBridgeBootstrap.swift
More file actions
161 lines (141 loc) · 6.25 KB
/
Copy pathAndroidBridgeBootstrap.swift
File metadata and controls
161 lines (141 loc) · 6.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// Copyright 2024–2025 Skip
// SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception
#if SKIP
import Foundation
import OSLog
fileprivate let logger: Logger = Logger(subsystem: "skip.android.bridge", category: "AndroidBridge")
#else
import Foundation
@_exported import SkipBridge
@_exported import SwiftJNI
#if canImport(FoundationNetworking)
@_exported import FoundationNetworking
#endif
#if canImport(AndroidLogging)
@_exported import AndroidLogging
#elseif canImport(OSLog)
@_exported import OSLog
#else
// e.g., for Linux define a local logging stub
class Logger {
let subsystem: String
let category: String
init(subsystem: String, category: String) {
self.subsystem = subsystem
self.category = category
}
func log(_ string: String) {
print("\(subsystem)/\(category): \(string)")
}
func debug(_ string: String) {
print("\(subsystem)/\(category): \(string)")
}
}
#endif
#if canImport(AndroidLooper)
@_exported import AndroidLooper
#endif
#if canImport(AndroidNative)
import AndroidNative
#endif
fileprivate let logger: Logger = Logger(subsystem: "skip.android.bridge", category: "AndroidBridge")
#endif
#if os(Android) || ROBOLECTRIC
public let isAndroid = true
#else
public let isAndroid = false
#endif
#if SKIP
/// The entry point from a Kotlin Main.kt into the bridged `SkipAndroidBridge`.
///
/// This class handles the initial Kotlin-side setup of the Swift bridging, which currently
/// just involves loading the specific library and calling the Swift `AndroidBridgeBootstrap.initAndroidBridge()`,
/// which will, in turn, perform all the Foundation-level setup.
public class AndroidBridge {
/// This is called at app initialization time by reflection from the `Main.kt`
///
/// It will look like: `skip.android.bridge.AndroidBridge.initBridge("AppDroidModel")`
public static func initBridge(_ libraryNames: String) throws {
for libraryName in libraryNames.split(separator: ",") {
do {
logger.debug("loading library: \(libraryName)")
try System.loadLibrary(libraryName)
} catch {
android.util.Log.e("SkipBridge", "error loading bridge library: \(libraryName)", error as? ErrorException)
}
}
let context = ProcessInfo.processInfo.androidContext
try AndroidBridgeBootstrap.initAndroidBridge(filesDir: context.getFilesDir().getAbsolutePath(), cacheDir: context.getCacheDir().getAbsolutePath())
}
}
#endif
/// Called from Kotlin's `AndroidBridge.initBridge` to perform setup that is needed to
/// get `Foundation` idioms working with Android conventions.
// SKIP @bridge
public class AndroidBridgeBootstrap {
private static var androidBridgeInit = false
/// Perform all the setup that is needed to get `Foundation` idioms working with Android conventions.
///
/// This includes:
/// - Using the Android certificate store for HTTPS validation
/// - Using the Android context file locations for `FileManager.url`
// SKIP @bridge
public static func initAndroidBridge(filesDir: String, cacheDir: String) throws {
if Self.androidBridgeInit == true { return }
defer { Self.androidBridgeInit = true }
let start = Date.now
logger.debug("initAndroidBridge: start")
#if os(Android) || ROBOLECTRIC
logger.debug("initAndroidBridge: bootstrapFileManagerProperties")
try bootstrapFileManagerProperties(filesDir: filesDir, cacheDir: cacheDir)
#endif
#if os(Android)
logger.debug("initAndroidBridge: AssetURLProtocol.register")
try AssetURLProtocol.register()
logger.debug("initAndroidBridge: bootstrapTimezone")
try bootstrapTimezone()
logger.debug("initAndroidBridge: setupCACerts")
try AndroidBootstrap.setupCACerts()
logger.debug("initAndroidBridge: AndroidLooper.setupMainLooper")
let _ = AndroidLooper.setupMainLooper()
logger.debug("initAndroidBridge: done")
#endif
logger.debug("AndroidBridgeBootstrap.initAndroidBridge done in \(Date.now.timeIntervalSince(start)) applicationSupportDirectory=\(URL.applicationSupportDirectory.path)")
}
}
private func bootstrapTimezone() throws {
// Until https://github.com/swiftlang/swift-foundation/pull/1053 gets merged
tzset()
var t = time(nil)
var lt : tm = tm()
localtime_r(&t, <)
if let zoneptr = lt.tm_zone, let name = String(validatingUTF8: zoneptr) {
//logger.debug("detected timezone: \(name)")
setenv("TZ", name, 0)
}
}
private func bootstrapFileManagerProperties(filesDir: String, cacheDir: String) throws {
// https://github.com/swiftlang/swift-foundation/blob/main/Sources/FoundationEssentials/FileManager/SearchPaths/FileManager%2BXDGSearchPaths.swift#L46
setenv("XDG_CACHE_HOME", cacheDir, 0)
// https://github.com/swiftlang/swift-foundation/blob/main/Sources/FoundationEssentials/FileManager/SearchPaths/FileManager%2BXDGSearchPaths.swift#L37
setenv("XDG_DATA_HOME", filesDir, 0)
// Also set the environment needed for UserDefaults to be able to persist to the filesystem
// https://github.com/swiftlang/swift-corelibs-foundation/blob/main/Sources/CoreFoundation/CFPlatform.c#L331C66-L331C83
setenv("CFFIXED_USER_HOME", filesDir, 0)
// ensure that we can get the `.applicationSupportDirectory`, which should use the `XDG_DATA_HOME` environment
//let applicationSupportDirectory = URL.applicationSupportDirectory // unavailable on Android
let applicationSupportDirectory = try! FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
logger.debug("bootstrapFileManagerProperties: applicationSupportDirectory=\(applicationSupportDirectory.path)")
}
// URL.applicationSupportDirectory exists in Darwin's Foundation but not in Android's Foundation
#if !canImport(Darwin)
// SKIP @nobridge
extension URL {
public static var applicationSupportDirectory: URL {
try! FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
}
public static var cachesDirectory: URL {
try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
}
}
#endif