Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ var package = Package(
),
.library(
name: "AndroidNDK", targets: ["AndroidNDK"]
),
.library(
name: "AndroidHardware", targets: ["AndroidHardware"]
)
],
dependencies: [
Expand Down Expand Up @@ -145,7 +148,8 @@ var package = Package(
"AndroidWidget",
"AndroidWebKit",
"AndroidLogging",
"AndroidLooper"
"AndroidLooper",
"AndroidHardware"
],
swiftSettings: [
.swiftLanguageMode(.v5),
Expand Down Expand Up @@ -448,6 +452,21 @@ var package = Package(
linkerSettings: [
.linkedLibrary("android", .when(platforms: [.android]))
]
),
.target(
name: "AndroidHardware",
dependencies: [
"AndroidNDK",
"AndroidLooper"
],
swiftSettings: [
.swiftLanguageMode(.v6),
ndkVersionDefine,
sdkVersionDefine
],
linkerSettings: [
.linkedLibrary("android", .when(platforms: [.android]))
]
)
],
swiftLanguageModes: [.v5, .v6]
Expand Down
13 changes: 13 additions & 0 deletions Sources/AndroidBinder/Error.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,17 @@ internal extension binder_status_t {
let error = AndroidBinderError(errorCode, file: file, function: function)
return .failure(error)
}

func mapError<T>(
as _: T.Type,
file: StaticString = #file,
function: StaticString = #function
) -> Result<T, AndroidBinderError> {
guard self != STATUS_OK else {
fatalError("mapError(as:) must only be used for non-STATUS_OK results.")
}
let errorCode = AndroidBinderError.ErrorCode(rawValue: self)
let error = AndroidBinderError(errorCode, file: file, function: function)
return .failure(error)
}
}
45 changes: 21 additions & 24 deletions Sources/AndroidBinder/Parcel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,8 @@ public extension Parcel {
func marshal(start: Int = 0, length: Int? = nil) throws(AndroidBinderError) -> [UInt8] {
let len = length ?? (dataSize - start)
var buffer = [UInt8](repeating: 0, count: len)
try buffer.withUnsafeMutableBufferPointer { buf in
if let base = buf.baseAddress {
try handle.marshal(into: base, start: start, length: len).get()
}
if let base = buffer.withUnsafeMutableBufferPointer({ $0.baseAddress }) {
try handle.marshal(into: base, start: start, length: len).get()
}
return buffer
}
Expand All @@ -151,10 +149,8 @@ public extension Parcel {
* \param data the bytes to unmarshal.
*/
func unmarshal(_ data: [UInt8]) throws(AndroidBinderError) {
try data.withUnsafeBufferPointer { buf in
if let base = buf.baseAddress {
try handle.unmarshal(base, length: data.count).get()
}
if let base = data.withUnsafeBufferPointer({ $0.baseAddress }) {
try handle.unmarshal(base, length: data.count).get()
}
}
}
Expand Down Expand Up @@ -800,24 +796,25 @@ internal extension Parcel.Handle {
func readString() -> Result<String, AndroidBinderError> {
var ctx = ParcelStringReadContext(buffer: nil)
let status = withUnsafeMutablePointer(to: &ctx) { ctxPtr -> binder_status_t in
AParcel_readString(pointer, ctxPtr) { userData, length -> UnsafeMutablePointer<CChar>? in
guard let userData = userData else { return nil }
AParcel_readString(pointer, ctxPtr) { userData, length, outBuffer -> Bool in
guard let userData = userData else { return false }
let buf = UnsafeMutablePointer<CChar>.allocate(capacity: Int(length) + 1)
buf[Int(length)] = 0
userData.assumingMemoryBound(to: ParcelStringReadContext.self).pointee.buffer = buf
return buf
outBuffer?.pointee = buf
return true
}
}
defer { ctx.buffer?.deallocate() }
guard status == STATUS_OK else { return status.mapError() }
guard status == STATUS_OK else { return status.mapError(as: String.self) }
guard let buf = ctx.buffer else { return .success("") }
return .success(String(cString: buf))
}

func readStrongBinder() -> Result<AndroidBinder, AndroidBinderError> {
var binderPtr: OpaquePointer? = nil
let status = AParcel_readStrongBinder(pointer, &binderPtr)
guard status == STATUS_OK else { return status.mapError() }
guard status == STATUS_OK else { return status.mapError(as: AndroidBinder.self) }
guard let ptr = binderPtr else {
return .failure(AndroidBinderError(AndroidBinderError.ErrorCode.unexpectedNull))
}
Expand All @@ -832,7 +829,7 @@ internal extension Parcel.Handle {
func readStatusHeader() -> Result<Status, AndroidBinderError> {
var statusPtr: OpaquePointer? = nil
let statusCode = AParcel_readStatusHeader(pointer, &statusPtr)
guard statusCode == STATUS_OK else { return statusCode.mapError() }
guard statusCode == STATUS_OK else { return statusCode.mapError(as: Status.self) }
guard let ptr = statusPtr else {
return .failure(AndroidBinderError(AndroidBinderError.ErrorCode.unexpectedNull))
}
Expand Down Expand Up @@ -979,7 +976,7 @@ internal extension Parcel.Handle {
}
}
defer { ctx.buffer?.deallocate() }
guard status == STATUS_OK else { return status.mapError() }
guard status == STATUS_OK else { return status.mapError(as: [Int8]?.self) }
if ctx.isNull { return .success(nil) }
if let buf = ctx.buffer {
return .success(Array(UnsafeBufferPointer(start: buf, count: Int(ctx.count))))
Expand All @@ -1004,7 +1001,7 @@ internal extension Parcel.Handle {
}
}
defer { ctx.buffer?.deallocate() }
guard status == STATUS_OK else { return status.mapError() }
guard status == STATUS_OK else { return status.mapError(as: [Int32]?.self) }
if ctx.isNull { return .success(nil) }
if let buf = ctx.buffer {
return .success(Array(UnsafeBufferPointer(start: buf, count: Int(ctx.count))))
Expand All @@ -1029,7 +1026,7 @@ internal extension Parcel.Handle {
}
}
defer { ctx.buffer?.deallocate() }
guard status == STATUS_OK else { return status.mapError() }
guard status == STATUS_OK else { return status.mapError(as: [UInt32]?.self) }
if ctx.isNull { return .success(nil) }
if let buf = ctx.buffer {
return .success(Array(UnsafeBufferPointer(start: buf, count: Int(ctx.count))))
Expand All @@ -1054,7 +1051,7 @@ internal extension Parcel.Handle {
}
}
defer { ctx.buffer?.deallocate() }
guard status == STATUS_OK else { return status.mapError() }
guard status == STATUS_OK else { return status.mapError(as: [Int64]?.self) }
if ctx.isNull { return .success(nil) }
if let buf = ctx.buffer {
return .success(Array(UnsafeBufferPointer(start: buf, count: Int(ctx.count))))
Expand All @@ -1079,7 +1076,7 @@ internal extension Parcel.Handle {
}
}
defer { ctx.buffer?.deallocate() }
guard status == STATUS_OK else { return status.mapError() }
guard status == STATUS_OK else { return status.mapError(as: [UInt64]?.self) }
if ctx.isNull { return .success(nil) }
if let buf = ctx.buffer {
return .success(Array(UnsafeBufferPointer(start: buf, count: Int(ctx.count))))
Expand All @@ -1104,7 +1101,7 @@ internal extension Parcel.Handle {
}
}
defer { ctx.buffer?.deallocate() }
guard status == STATUS_OK else { return status.mapError() }
guard status == STATUS_OK else { return status.mapError(as: [Float]?.self) }
if ctx.isNull { return .success(nil) }
if let buf = ctx.buffer {
return .success(Array(UnsafeBufferPointer(start: buf, count: Int(ctx.count))))
Expand All @@ -1129,7 +1126,7 @@ internal extension Parcel.Handle {
}
}
defer { ctx.buffer?.deallocate() }
guard status == STATUS_OK else { return status.mapError() }
guard status == STATUS_OK else { return status.mapError(as: [Double]?.self) }
if ctx.isNull { return .success(nil) }
if let buf = ctx.buffer {
return .success(Array(UnsafeBufferPointer(start: buf, count: Int(ctx.count))))
Expand All @@ -1154,7 +1151,7 @@ internal extension Parcel.Handle {
}
}
defer { ctx.buffer?.deallocate() }
guard status == STATUS_OK else { return status.mapError() }
guard status == STATUS_OK else { return status.mapError(as: [UInt16]?.self) }
if ctx.isNull { return .success(nil) }
if let buf = ctx.buffer {
return .success(Array(UnsafeBufferPointer(start: buf, count: Int(ctx.count))))
Expand Down Expand Up @@ -1184,7 +1181,7 @@ internal extension Parcel.Handle {
)
}
defer { ctx.elements?.deallocate() }
guard status == STATUS_OK else { return status.mapError() }
guard status == STATUS_OK else { return status.mapError(as: [Bool]?.self) }
if ctx.isNull { return .success(nil) }
if let elements = ctx.elements {
return .success(Array(UnsafeBufferPointer(start: elements, count: Int(ctx.count))))
Expand Down Expand Up @@ -1255,4 +1252,4 @@ private struct ParcelUInt16ArrayReadContext {
var buffer: UnsafeMutablePointer<UInt16>?
var count: Int32
var isNull: Bool
}
}
2 changes: 1 addition & 1 deletion Sources/AndroidBinder/Syscalls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func AParcel_readDouble(_ parcel: OpaquePointer, _ outValue: UnsafeMutablePointe
func AParcel_readBool(_ parcel: OpaquePointer, _ outValue: UnsafeMutablePointer<Bool>) -> binder_status_t { stub() }
func AParcel_readChar(_ parcel: OpaquePointer, _ outValue: UnsafeMutablePointer<UInt16>) -> binder_status_t { stub() }
func AParcel_readByte(_ parcel: OpaquePointer, _ outValue: UnsafeMutablePointer<Int8>) -> binder_status_t { stub() }
func AParcel_readString(_ parcel: OpaquePointer, _ stringData: UnsafeMutableRawPointer?, _ allocator: (@convention(c) (UnsafeMutableRawPointer?, Int32) -> UnsafeMutablePointer<CChar>?)?) -> binder_status_t { stub() }
func AParcel_readString(_ parcel: OpaquePointer, _ stringData: UnsafeMutableRawPointer?, _ allocator: (@convention(c) (UnsafeMutableRawPointer?, Int32, UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?) -> Bool)?) -> binder_status_t { stub() }
func AParcel_readStrongBinder(_ parcel: OpaquePointer, _ outBinder: UnsafeMutablePointer<OpaquePointer?>) -> binder_status_t { stub() }
func AParcel_readParcelFileDescriptor(_ parcel: OpaquePointer, _ outFd: UnsafeMutablePointer<Int32>) -> binder_status_t { stub() }
func AParcel_readStatusHeader(_ parcel: OpaquePointer, _ outStatus: UnsafeMutablePointer<OpaquePointer?>) -> binder_status_t { stub() }
Expand Down
31 changes: 31 additions & 0 deletions Sources/AndroidHardware/Error.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Error.swift
// SwiftAndroid
//
// Created by Alsey Coleman Miller on 7/6/25.
//

/// Android Sensor Error
public enum AndroidSensorError: Swift.Error {

/// Unable to get sensor manager instance.
case invalidManager

/// Unable to create event queue.
case createEventQueue

/// Unable to enable sensor (result code).
case enableSensor(Int32)

/// Unable to disable sensor (result code).
case disableSensor(Int32)

/// Unable to register sensor (result code).
case registerSensor(Int32)

/// Unable to set event rate (result code).
case setEventRate(Int32)

/// Error reading sensor events (result code).
case getEvents(Int32)
}
115 changes: 115 additions & 0 deletions Sources/AndroidHardware/Sensor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//
// Sensor.swift
// SwiftAndroid
//
// Created by Alsey Coleman Miller on 7/6/25.
//

#if os(Android)
import Android
import AndroidNDK
#endif

/// A reference to a hardware sensor.
///
/// Sensor instances are vended by `SensorManager` and do not have independent
/// ownership — the underlying pointer is valid for the lifetime of the
/// `SensorManager` that created it.
public struct Sensor: @unchecked Sendable {

internal let pointer: OpaquePointer

internal init(_ pointer: OpaquePointer) {
self.pointer = pointer
}
}

// MARK: - Properties

public extension Sensor {

/// The name string of the sensor.
var name: String {
guard let cStr = ASensor_getName(pointer) else { return "" }
return String(cString: cStr)
}

/// The vendor string of the sensor.
var vendor: String {
guard let cStr = ASensor_getVendor(pointer) else { return "" }
return String(cString: cStr)
}

/// The type of the sensor.
var type: SensorType {
SensorType(rawValue: ASensor_getType(pointer))
}

/// The resolution of the sensor in the sensor's unit.
var resolution: Float {
ASensor_getResolution(pointer)
}

/// The minimum delay in microseconds between two events, or 0 if the sensor
/// reports only when values change.
var minDelay: Int32 {
ASensor_getMinDelay(pointer)
}

/// The maximum number of events that the hardware FIFO can hold.
var fifoMaxEventCount: Int32 {
ASensor_getFifoMaxEventCount(pointer)
}

/// The number of events reserved for this sensor in the hardware FIFO.
var fifoReservedEventCount: Int32 {
ASensor_getFifoReservedEventCount(pointer)
}

/// The type string of the sensor (e.g. `"android.sensor.accelerometer"`).
var stringType: String? {
ASensor_getStringType(pointer).map { String(cString: $0) }
}

/// The reporting mode of the sensor.
var reportingMode: SensorReportingMode? {
SensorReportingMode(rawValue: ASensor_getReportingMode(pointer))
}

/// Whether this sensor is a wake-up sensor.
var isWakeUpSensor: Bool {
ASensor_isWakeUpSensor(pointer)
}

/// The hardware sensor handle, unique within a device.
var handle: Int32 {
ASensor_getHandle(pointer)
}
}

// MARK: - Equatable

extension Sensor: Equatable {

public static func == (lhs: Sensor, rhs: Sensor) -> Bool {
lhs.pointer == rhs.pointer
}
}

// MARK: - Hashable

extension Sensor: Hashable {

public func hash(into hasher: inout Hasher) {
hasher.combine(pointer)
}
}

// MARK: - CustomStringConvertible

extension Sensor: CustomStringConvertible {

public var description: String {
"\(name) (\(vendor))"
}
}
Loading
Loading