mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
114 lines
No EOL
3.7 KiB
Kotlin
114 lines
No EOL
3.7 KiB
Kotlin
package com.geeksville.mesh.android
|
|
|
|
import android.content.ComponentName
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.content.ServiceConnection
|
|
import android.os.IBinder
|
|
import android.os.IInterface
|
|
import com.geeksville.mesh.util.exceptionReporter
|
|
import java.io.Closeable
|
|
import java.lang.IllegalArgumentException
|
|
import java.util.concurrent.locks.ReentrantLock
|
|
import kotlin.concurrent.withLock
|
|
|
|
class BindFailedException : Exception("bindService failed")
|
|
|
|
/**
|
|
* A wrapper that cleans up the service binding process
|
|
*/
|
|
open class ServiceClient<T : IInterface>(private val stubFactory: (IBinder) -> T) : Closeable,
|
|
Logging {
|
|
|
|
var serviceP: T? = null
|
|
|
|
/// A getter that returns the bound service or throws if not bound
|
|
val service: T
|
|
get() {
|
|
waitConnect() // Wait for at least the initial connection to happen
|
|
return serviceP ?: throw Exception("Service not bound")
|
|
}
|
|
|
|
private var context: Context? = null
|
|
|
|
private var isClosed = true
|
|
|
|
private val lock = ReentrantLock()
|
|
private val condition = lock.newCondition()
|
|
|
|
/** Call this if you want to stall until the connection is completed */
|
|
fun waitConnect() {
|
|
// Wait until this service is connected
|
|
lock.withLock {
|
|
if (context == null)
|
|
throw Exception("Haven't called connect")
|
|
|
|
if (serviceP == null)
|
|
condition.await()
|
|
}
|
|
}
|
|
|
|
fun connect(c: Context, intent: Intent, flags: Int) {
|
|
context = c
|
|
if (isClosed) {
|
|
isClosed = false
|
|
if (!c.bindService(intent, connection, flags)) {
|
|
|
|
// Some phones seem to ahve a race where if you unbind and quickly rebind bindService returns false. Try
|
|
// a short sleep to see if that helps
|
|
errormsg("Needed to use the second bind attempt hack")
|
|
Thread.sleep(500) // was 200ms, but received an autobug from a Galaxy Note4, android 6.0.1
|
|
if (!c.bindService(intent, connection, flags)) {
|
|
throw BindFailedException()
|
|
}
|
|
}
|
|
} else {
|
|
warn("Ignoring rebind attempt for service")
|
|
}
|
|
}
|
|
|
|
override fun close() {
|
|
isClosed = true
|
|
try {
|
|
context?.unbindService(connection)
|
|
}
|
|
catch(ex: IllegalArgumentException) {
|
|
// Autobugs show this can generate an illegal arg exception for "service not registered" during reinstall?
|
|
warn("Ignoring error in ServiceClient.close, probably harmless")
|
|
}
|
|
serviceP = null
|
|
context = null
|
|
}
|
|
|
|
/// Called when we become connected
|
|
open fun onConnected(service: T) {
|
|
}
|
|
|
|
/// called on loss of connection
|
|
open fun onDisconnected() {
|
|
}
|
|
|
|
private val connection = object : ServiceConnection {
|
|
override fun onServiceConnected(name: ComponentName, binder: IBinder) = exceptionReporter {
|
|
if (!isClosed) {
|
|
val s = stubFactory(binder)
|
|
serviceP = s
|
|
onConnected(s)
|
|
|
|
// after calling our handler, tell anyone who was waiting for this connection to complete
|
|
lock.withLock {
|
|
condition.signalAll()
|
|
}
|
|
} else {
|
|
// If we start to close a service, it seems that there is a possibility a onServiceConnected event is the queue
|
|
// for us. Be careful not to process that stale event
|
|
warn("A service connected while we were closing it, ignoring")
|
|
}
|
|
}
|
|
|
|
override fun onServiceDisconnected(name: ComponentName?) = exceptionReporter {
|
|
serviceP = null
|
|
onDisconnected()
|
|
}
|
|
}
|
|
} |