/* * SPDX-FileCopyrightText: 2020, microG Project Team * SPDX-License-Identifier: Apache-2.0 */ package org.microg.nlp.client import android.content.ComponentName import android.content.Context import android.content.ServiceConnection import android.location.Location import android.os.Bundle import androidx.lifecycle.Lifecycle import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.microg.nlp.service.api.* import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine class LocationClient(context: Context, lifecycle: Lifecycle) : BaseClient(context, lifecycle, { ILocationService.Stub.asInterface(it) }) { private val requests = hashSetOf() private val requestsMutex = Mutex(false) override val action: String get() = Constants.ACTION_LOCATION suspend fun getLastLocation(options: Bundle = defaultOptions): Location? = withService { service -> suspendCoroutine { service.getLastLocation(SingleLocationListener(it), options) } } suspend fun getLastLocationForBackend(componentName: ComponentName, options: Bundle = defaultOptions) = getLastLocationForBackend(componentName.packageName, componentName.className, null, options) suspend fun getLastLocationForBackend(packageName: String, className: String, signatureDigest: String? = null, options: Bundle = defaultOptions): Location? = withService { service -> suspendCoroutine { service.getLastLocationForBackend(packageName, className, signatureDigest, SingleLocationListener(it), options) } } private suspend fun withRequestService(v: suspend (ILocationService) -> T): T { return requestsMutex.withLock { try { if (requests.isEmpty()) connect() withService(v) } finally { if (requests.isEmpty()) disconnect() } } } suspend fun updateLocationRequest(request: LocationRequest, options: Bundle = defaultOptions): Unit = withRequestService { service -> suspendCoroutine { service.updateLocationRequest(request, StatusCallback(it), options) } requests.removeAll { it.id == request.id } requests.add(request) } suspend fun cancelLocationRequestByListener(listener: ILocationListener, options: Bundle = defaultOptions): Unit = withRequestService { service -> suspendCoroutine { service.cancelLocationRequestByListener(listener, StatusCallback(it), options) } requests.removeAll { it.listener == listener } } suspend fun cancelLocationRequestById(id: String, options: Bundle = defaultOptions): Unit = withRequestService { service -> suspendCoroutine { service.cancelLocationRequestById(id, StatusCallback(it), options) } requests.removeAll { it.id == id } } suspend fun forceLocationUpdate(options: Bundle = defaultOptions): Unit = withService { service -> suspendCoroutine { service.forceLocationUpdate(StatusCallback(it), options) } } suspend fun getLocationBackends(options: Bundle = defaultOptions): List = withService { service -> suspendCoroutine { service.getLocationBackends(StringsCallback(it), options) } } suspend fun setLocationBackends(backends: List, options: Bundle = defaultOptions): Unit = withService { service -> suspendCoroutine { service.setLocationBackends(backends, StatusCallback(it), options) } } } private class SingleLocationListener(private val continuation: Continuation) : ILocationListener.Stub() { override fun onLocation(statusCode: Int, location: Location?) { if (statusCode == Constants.STATUS_OK) { continuation.resume(location) } else { continuation.resumeWithException(RuntimeException("Status: $statusCode")) } } }