/* * SPDX-FileCopyrightText: 2020, microG Project Team * SPDX-License-Identifier: Apache-2.0 */ package org.microg.nlp.service import android.content.Intent import android.location.Address import android.os.Bundle import android.os.Handler import android.os.IBinder import android.os.Looper import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.microg.nlp.api.GeocoderBackend import java.util.concurrent.* import kotlin.coroutines.suspendCoroutine class AsyncGeocoderBackend(binder: IBinder, name: String = "geocoder-backend-thread") : Thread(name) { private val syncThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue()) private lateinit var looper: Looper private lateinit var handler: Handler private val mutex = Mutex(true) private val backend = GeocoderBackend.Stub.asInterface(binder) override fun run() { Looper.prepare() looper = Looper.myLooper()!! handler = Handler(looper) handler.post { mutex.unlock() } Looper.loop() } suspend fun open() { start() mutex.withLock { suspendCoroutine { handler.post { val result = try { backend.open() Result.success(Unit) } catch (e: Exception) { Result.failure(e) } it.resumeWith(result) } } } } suspend fun getFromLocation(latitude: Double, longitude: Double, maxResults: Int, locale: String?): List
= mutex.withLock { suspendCoroutine { handler.post { val result = try { Result.success(backend.getFromLocation(latitude, longitude, maxResults, locale)) } catch (e: Exception) { Result.failure>(e) } it.resumeWith(result) } } } fun getFromLocationSync(latitude: Double, longitude: Double, maxResults: Int, locale: String?): List
= executeWithTimeout { backend.getFromLocation(latitude, longitude, maxResults, locale); } suspend fun getFromLocationName(locationName: String?, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String?): List
= mutex.withLock { suspendCoroutine { handler.post { val result = try { Result.success(backend.getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale)) } catch (e: Exception) { Result.failure>(e) } it.resumeWith(result) } } } fun getFromLocationNameSync(locationName: String?, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String?): List
= executeWithTimeout { backend.getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale) } suspend fun close() { mutex.withLock { suspendCoroutine { handler.post { val result = try { backend.close() Result.success(Unit) } catch (e: Exception) { Result.failure(e) } it.resumeWith(result) } } } looper.quit() } suspend fun getSettingsIntent(): Intent = mutex.withLock { suspendCoroutine { handler.post { val result = try { Result.success(backend.settingsIntent) } catch (e: Exception) { Result.failure(e) } it.resumeWith(result) } } } suspend fun getInitIntent(): Intent = mutex.withLock { suspendCoroutine { handler.post { val result = try { Result.success(backend.initIntent) } catch (e: Exception) { Result.failure(e) } it.resumeWith(result) } } } suspend fun getAboutIntent(): Intent = mutex.withLock { suspendCoroutine { handler.post { val result = try { Result.success(backend.aboutIntent) } catch (e: Exception) { Result.failure(e) } it.resumeWith(result) } } } suspend fun getFromLocationWithOptions(latitude: Double, longitude: Double, maxResults: Int, locale: String?, options: Bundle?): List
= mutex.withLock { suspendCoroutine { handler.post { val result = try { Result.success(backend.getFromLocationWithOptions(latitude, longitude, maxResults, locale, options)) } catch (e: Exception) { Result.failure>(e) } it.resumeWith(result) } } } fun getFromLocationWithOptionsSync(latitude: Double, longitude: Double, maxResults: Int, locale: String?, options: Bundle?): List
= executeWithTimeout { backend.getFromLocationWithOptions(latitude, longitude, maxResults, locale, options) } suspend fun getFromLocationNameWithOptions(locationName: String?, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String?, options: Bundle?): List
= mutex.withLock { suspendCoroutine { handler.post { val result = try { Result.success(backend.getFromLocationNameWithOptions(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale, options)) } catch (e: Exception) { Result.failure>(e) } it.resumeWith(result) } } } fun getFromLocationNameWithOptionsSync(locationName: String?, maxResults: Int, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, locale: String?, options: Bundle?): List
= executeWithTimeout { backend.getFromLocationNameWithOptions(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale, options) } private fun executeWithTimeout(timeout: Long = CALL_TIMEOUT, action: () -> T): T { var result: T? = null val latch = CountDownLatch(1) var err: Exception? = null syncThreads.execute { try { result = action() } catch (e: Exception) { err = e } finally { latch.countDown() } } if (!latch.await(timeout, TimeUnit.MILLISECONDS)) throw TimeoutException() err?.let { throw it } return result ?: throw NullPointerException() } companion object { private const val CALL_TIMEOUT = 10000L } }