Loading build.gradle +4 −3 Original line number Diff line number Diff line Loading @@ -5,12 +5,13 @@ buildscript { ext.kotlinVersion = '1.3.72' ext.coroutineVersion = '1.3.3' ext.coroutineVersion = '1.3.7' ext.appcompatVersion = '1.1.0' ext.fragmentVersion = '1.2.4' ext.recyclerviewVersion = '1.1.0' ext.fragmentVersion = '1.2.5' ext.lifecycleVersion = '2.2.0' ext.navigationVersion = '2.3.0' ext.recyclerviewVersion = '1.1.0' ext.androidBuildGradleVersion = '3.6.3' Loading client/src/main/kotlin/org/microg/nlp/client/UnifiedLocationClient.kt +138 −79 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger import android.content.pm.PackageManager.SIGNATURE_MATCH import kotlinx.coroutines.* import java.lang.RuntimeException import java.util.concurrent.* import kotlin.coroutines.* import kotlin.math.min Loading @@ -47,6 +48,7 @@ class UnifiedLocationClient private constructor(context: Context) { private var timer: Timer? = null private var reconnectCount = 0 private val requests = CopyOnWriteArraySet<LocationRequest>() private val coroutineScope = CoroutineScope(Dispatchers.IO + Job()) private val connection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) { this@UnifiedLocationClient.onServiceConnected(name, service) Loading @@ -70,6 +72,10 @@ class UnifiedLocationClient private constructor(context: Context) { get() = options.getBoolean(KEY_FORCE_NEXT_UPDATE, false) set(value) = options.putBoolean(KEY_FORCE_NEXT_UPDATE, value) var opPackageName: String? get() = options.getString(KEY_OP_PACKAGE_NAME) set(value) = options.putString(KEY_OP_PACKAGE_NAME, value) val isAvailable: Boolean get() = bound || resolve() != null Loading @@ -83,44 +89,40 @@ class UnifiedLocationClient private constructor(context: Context) { val pm = context.packageManager var resolveInfos = pm.queryIntentServices(intent, 0) if (resolveInfos.size > 1) { // Pick own package if possible for (info in resolveInfos) { if (info.serviceInfo.packageName == context.packageName) { intent.setPackage(info.serviceInfo.packageName) Log.d(TAG, "Found more than one active unified service, picked own package " + intent.getPackage()!!) return intent } } // Pick package with matching signature if possible for (info in resolveInfos) { if (pm.checkSignatures(context.packageName, info.serviceInfo.packageName) == SIGNATURE_MATCH) { intent.setPackage(info.serviceInfo.packageName) Log.d(TAG, "Found more than one active unified service, picked related package " + intent.getPackage()!!) return intent // Restrict to self if possible val isSelf: (it: ResolveInfo) -> Boolean = { it.serviceInfo.packageName == context.packageName } if (resolveInfos.size > 1 && resolveInfos.any(isSelf)) { Log.d(TAG, "Found more than one active unified service, restricted to own package " + context.packageName) resolveInfos = resolveInfos.filter(isSelf) } // Restrict to system if single package is system if (resolveInfos.any { (it.serviceInfo?.applicationInfo?.flags ?: 0) and ApplicationInfo.FLAG_SYSTEM > 0 }) { Log.d(TAG, "Found more than one active unified service, restricted to system packages") resolveInfos = resolveInfos.filter { (it.serviceInfo?.applicationInfo?.flags ?: 0) and ApplicationInfo.FLAG_SYSTEM > 0 // Restrict to package with matching signature if possible val isSelfSig: (it: ResolveInfo) -> Boolean = { it.serviceInfo.packageName == context.packageName } if (resolveInfos.size > 1 && resolveInfos.any(isSelfSig)) { Log.d(TAG, "Found more than one active unified service, restricted to related packages") resolveInfos = resolveInfos.filter(isSelfSig) } var highestPriority: ResolveInfo? = null for (info in resolveInfos) { if (highestPriority == null || highestPriority.priority < info.priority) { highestPriority = info // Restrict to system if any package is system val isSystem: (it: ResolveInfo) -> Boolean = { (it.serviceInfo?.applicationInfo?.flags ?: 0) and ApplicationInfo.FLAG_SYSTEM > 0 } if (resolveInfos.size > 1 && resolveInfos.any(isSystem)) { Log.d(TAG, "Found more than one active unified service, restricted to system packages") resolveInfos = resolveInfos.filter(isSystem) } val highestPriority: ResolveInfo? = resolveInfos.maxBy { it.priority } intent.setPackage(highestPriority!!.serviceInfo.packageName) Log.d(TAG, "Found more than one active unified service, picked highest priority " + intent.getPackage()!!) intent.setClassName(highestPriority.serviceInfo.packageName, highestPriority.serviceInfo.name) if (resolveInfos.size > 1) { Log.d(TAG, "Found more than one active unified service, picked highest priority " + intent.component) } return intent } else if (!resolveInfos.isEmpty()) { intent.setPackage(resolveInfos[0].serviceInfo.packageName) Loading @@ -132,17 +134,21 @@ class UnifiedLocationClient private constructor(context: Context) { } @Synchronized private fun updateBinding() { private fun updateBinding(): Boolean { Log.d(TAG, "updateBinding - current: $bound, refs: ${serviceReferenceCount.get()}, reqs: ${requests.size}, avail: $isAvailable") if (!bound && (serviceReferenceCount.get() > 0 || !requests.isEmpty()) && isAvailable) { timer = Timer("unified-client") bound = true bind() return true } else if (bound && serviceReferenceCount.get() == 0 && requests.isEmpty()) { timer!!.cancel() timer = null bound = false unbind() return false } return bound } @Synchronized Loading Loading @@ -173,27 +179,30 @@ class UnifiedLocationClient private constructor(context: Context) { val intent = resolve() ?: return unbind() reconnectCount++ Log.d(TAG, "Binding to $intent") bound = context.bindService(intent, connection, Context.BIND_AUTO_CREATE) } @Synchronized private fun unbind() { try { this.context.get()?.let { it.unbindService(connection) } this.context.get()?.unbindService(connection) } catch (ignored: Exception) { } this.service = null } @Synchronized fun ref() { Log.d(TAG, "ref: ${Exception().stackTrace[1]}") serviceReferenceCount.incrementAndGet() updateBinding() } @Synchronized fun unref() { Log.d(TAG, "unref: ${Exception().stackTrace[1]}") serviceReferenceCount.decrementAndGet() updateBinding() } Loading @@ -210,24 +219,34 @@ class UnifiedLocationClient private constructor(context: Context) { requestLocationUpdates(listener, interval, Integer.MAX_VALUE) } @Synchronized fun requestLocationUpdates(listener: LocationListener, interval: Long, count: Int) { requests.removeAll(requests.filter { it.listener === listener }) requests.add(LocationRequest(listener, interval, count)) coroutineScope.launch { updateServiceInterval() updateBinding() } } @Synchronized fun removeLocationUpdates(listener: LocationListener) { coroutineScope.launch { removeRequests(requests.filter { it.listener === listener }) } } private suspend fun refAndGetService(): UnifiedLocationService = suspendCoroutine { continuation -> refAndGetServiceContinued(continuation) } @Synchronized private fun refAndGetServiceContinued(continuation: Continuation<UnifiedLocationService>) { Log.d(TAG, "ref+get: ${Exception().stackTrace[2]}") serviceReferenceCount.incrementAndGet() waitForServiceContinued(continuation) } private suspend fun waitForService(): UnifiedLocationService = suspendCoroutine { continuation -> waitForServiceContinued(continuation) } @Synchronized private fun waitForServiceContinued(continuation: Continuation<UnifiedLocationService>) { val service = service if (service != null) { continuation.resume(service) Loading @@ -235,7 +254,15 @@ class UnifiedLocationClient private constructor(context: Context) { synchronized(waitingForService) { waitingForService.add(continuation) } timer?.schedule(object : TimerTask() { updateBinding() val timer = timer if (timer == null) { synchronized(waitingForService) { waitingForService.remove(continuation) } continuation.resumeWithException(RuntimeException("No timer, called waitForService when not connected")) } else { timer.schedule(object : TimerTask() { override fun run() { try { continuation.resumeWithException(TimeoutException()) Loading @@ -244,13 +271,13 @@ class UnifiedLocationClient private constructor(context: Context) { } } }, CALL_TIMEOUT) updateBinding() } } } fun <T> configureContinuationTimeout(continuation: Continuation<T>, timeout: Long) { private fun <T> configureContinuationTimeout(continuation: Continuation<T>, timeout: Long) { if (timeout <= 0 || timeout == Long.MAX_VALUE) return timer?.schedule(object : TimerTask() { timer!!.schedule(object : TimerTask() { override fun run() { try { Log.w(TAG, "Timeout reached") Loading @@ -262,17 +289,24 @@ class UnifiedLocationClient private constructor(context: Context) { }, timeout) } fun <T> executeSyncWithTimeout(timeout: Long = CALL_TIMEOUT, action: suspend () -> T): T { private fun <T> executeSyncWithTimeout(timeout: Long = CALL_TIMEOUT, action: suspend () -> T): T { var result: T? = null val latch = CountDownLatch(1) var err: Exception? = null syncThreads.execute { try { runBlocking { result = withTimeout(timeout) { 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() } Loading Loading @@ -302,7 +336,7 @@ class UnifiedLocationClient private constructor(context: Context) { service.getFromLocationNameWithOptions(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale, options, AddressContinuation(continuation)) configureContinuationTimeout(continuation, timeout) } } catch (e: RemoteException) { } catch (e: Exception) { Log.w(TAG, "Failed to request geocode", e) emptyList() } finally { Loading @@ -327,7 +361,7 @@ class UnifiedLocationClient private constructor(context: Context) { suspend fun getLocationBackends(): Array<String> { try { return refAndGetService().locationBackends } catch (e: RemoteException) { } catch (e: Exception) { Log.w(TAG, "Failed to handle request", e) return emptyArray() } finally { Loading @@ -338,7 +372,7 @@ class UnifiedLocationClient private constructor(context: Context) { suspend fun setLocationBackends(backends: Array<String>) { try { refAndGetService().locationBackends = backends } catch (e: RemoteException) { } catch (e: Exception) { Log.w(TAG, "Failed to handle request", e) } finally { unref() Loading Loading @@ -366,6 +400,10 @@ class UnifiedLocationClient private constructor(context: Context) { } } fun getLastLocationSync(timeout: Long = CALL_TIMEOUT): Location? = executeSyncWithTimeout(timeout) { getLastLocation() } suspend fun getLastLocation(): Location? { return try { refAndGetService().lastLocation Loading @@ -388,12 +426,11 @@ class UnifiedLocationClient private constructor(context: Context) { } } @Synchronized private fun removeRequestPendingRemoval() { private suspend fun removeRequestPendingRemoval() { removeRequests(requests.filter { it.needsRemoval }) } private fun removeRequests(removalNeeded: List<LocationRequest>) { private suspend fun removeRequests(removalNeeded: List<LocationRequest>) { if (removalNeeded.isNotEmpty()) { requests.removeAll(removalNeeded) updateServiceInterval() Loading @@ -401,9 +438,7 @@ class UnifiedLocationClient private constructor(context: Context) { } } @Synchronized private fun updateServiceInterval() { if (service == null) return private suspend fun updateServiceInterval() { var interval: Long = Long.MAX_VALUE var requestSingle = false for (request in requests) { Loading @@ -419,11 +454,18 @@ class UnifiedLocationClient private constructor(context: Context) { } else { Log.d(TAG, "Set update interval to $interval") } val service: UnifiedLocationService try { service!!.setUpdateInterval(interval, options) service = waitForService() } catch (e: Exception) { Log.w(TAG, e) return } try { service.setUpdateInterval(interval, options) if (requestSingle) { Log.d(TAG, "Request single update (force update: $forceNextUpdate)") service!!.requestSingleUpdate(options) service.requestSingleUpdate(options) forceNextUpdate = false } } catch (e: DeadObjectException) { Loading @@ -434,7 +476,6 @@ class UnifiedLocationClient private constructor(context: Context) { } } @Synchronized private fun onServiceConnected(name: ComponentName, binder: IBinder) { Log.d(TAG, "Connected to $name") Loading @@ -446,24 +487,40 @@ class UnifiedLocationClient private constructor(context: Context) { continuations.addAll(waitingForService) waitingForService.clear() } for (continuation in continuations) { continuation.resume(service) } coroutineScope.launch { try { Log.d(TAG, "Registering location callback") service.registerLocationCallback(object : LocationCallback.Stub() { override fun onLocationUpdate(location: Location) { for (request in requests) { request.handleLocation(location) coroutineScope.launch { this@UnifiedLocationClient.onLocationUpdate(location) } removeRequestPendingRemoval() } }, options) updateServiceInterval() Log.d(TAG, "Registered location callback") } catch (e: Exception) { Log.w(TAG, "Failed to register location callback", e) } updateServiceInterval() if (continuations.size > 0) { Log.d(TAG, "Resuming ${continuations.size} continuations") } for (continuation in continuations) { try { continuation.resume(service) } catch (e: Exception) { Log.w(TAG, e) } } } } private suspend fun onLocationUpdate(location: Location) { for (request in requests) { request.handleLocation(location) } removeRequestPendingRemoval() } @Synchronized private fun onServiceDisconnected(name: ComponentName) { Loading Loading @@ -531,7 +588,9 @@ class UnifiedLocationClient private constructor(context: Context) { companion object { const val ACTION_UNIFIED_LOCATION_SERVICE = "org.microg.nlp.service.UnifiedLocationService" const val KEY_FORCE_NEXT_UPDATE = "org.microg.nlp.client.FORCE_NEXT_UPDATE" const val PERMISSION_SERVICE_ADMIN = "org.microg.nlp.SERVICE_ADMIN" const val KEY_FORCE_NEXT_UPDATE = "org.microg.nlp.FORCE_NEXT_UPDATE" const val KEY_OP_PACKAGE_NAME = "org.microg.nlp.OP_PACKAGE_NAME" private val TAG = "ULocClient" private var client: UnifiedLocationClient? = null Loading location-v2/src/main/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ <uses-permission android:name="android.permission.ACCESS_COARSE_UPDATES" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="org.microg.nlp.SERVICE_ADMIN" /> <application> <uses-library android:name="com.android.location.provider" /> Loading location-v2/src/main/java/org/microg/nlp/location/v2/LocationProvider.java +21 −0 Original line number Diff line number Diff line Loading @@ -18,9 +18,14 @@ import com.android.location.provider.ProviderRequestUnbundled; import org.microg.nlp.client.UnifiedLocationClient; import java.lang.reflect.Field; import java.util.Arrays; import java.util.List; import static android.location.LocationProvider.AVAILABLE; public class LocationProvider extends LocationProviderBase implements UnifiedLocationClient.LocationListener { private static final List<String> EXCLUDED_PACKAGES = Arrays.asList("android", "com.android.location.fused", "com.google.android.gms"); private static final long FASTEST_REFRESH_INTERVAL = 30000; private static final String TAG = "ULocation"; private Context context; Loading @@ -43,6 +48,21 @@ public class LocationProvider extends LocationProviderBase implements UnifiedLoc @Override public void onSetRequest(ProviderRequestUnbundled requests, WorkSource source) { Log.v(TAG, "onSetRequest: " + requests + " by " + source); String opPackageName = null; try { Field namesField = WorkSource.class.getDeclaredField("mNames"); namesField.setAccessible(true); String[] names = (String[]) namesField.get(source); if (names != null) { for (String name : names) { if (!EXCLUDED_PACKAGES.contains(name)) { opPackageName = name; break; } } } } catch (Exception ignored) { } long autoTime = Math.max(requests.getInterval(), FASTEST_REFRESH_INTERVAL); boolean autoUpdate = requests.getReportLocation(); Loading @@ -50,6 +70,7 @@ public class LocationProvider extends LocationProviderBase implements UnifiedLoc Log.v(TAG, "using autoUpdate=" + autoUpdate + " autoTime=" + autoTime); if (autoUpdate) { UnifiedLocationClient.get(context).setOpPackageName(opPackageName); UnifiedLocationClient.get(context).requestLocationUpdates(this, autoTime); } else { UnifiedLocationClient.get(context).removeLocationUpdates(this); Loading service/build.gradle +3 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,9 @@ dependencies { implementation project(':api') implementation project(':client') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" } afterEvaluate { Loading Loading
build.gradle +4 −3 Original line number Diff line number Diff line Loading @@ -5,12 +5,13 @@ buildscript { ext.kotlinVersion = '1.3.72' ext.coroutineVersion = '1.3.3' ext.coroutineVersion = '1.3.7' ext.appcompatVersion = '1.1.0' ext.fragmentVersion = '1.2.4' ext.recyclerviewVersion = '1.1.0' ext.fragmentVersion = '1.2.5' ext.lifecycleVersion = '2.2.0' ext.navigationVersion = '2.3.0' ext.recyclerviewVersion = '1.1.0' ext.androidBuildGradleVersion = '3.6.3' Loading
client/src/main/kotlin/org/microg/nlp/client/UnifiedLocationClient.kt +138 −79 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger import android.content.pm.PackageManager.SIGNATURE_MATCH import kotlinx.coroutines.* import java.lang.RuntimeException import java.util.concurrent.* import kotlin.coroutines.* import kotlin.math.min Loading @@ -47,6 +48,7 @@ class UnifiedLocationClient private constructor(context: Context) { private var timer: Timer? = null private var reconnectCount = 0 private val requests = CopyOnWriteArraySet<LocationRequest>() private val coroutineScope = CoroutineScope(Dispatchers.IO + Job()) private val connection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) { this@UnifiedLocationClient.onServiceConnected(name, service) Loading @@ -70,6 +72,10 @@ class UnifiedLocationClient private constructor(context: Context) { get() = options.getBoolean(KEY_FORCE_NEXT_UPDATE, false) set(value) = options.putBoolean(KEY_FORCE_NEXT_UPDATE, value) var opPackageName: String? get() = options.getString(KEY_OP_PACKAGE_NAME) set(value) = options.putString(KEY_OP_PACKAGE_NAME, value) val isAvailable: Boolean get() = bound || resolve() != null Loading @@ -83,44 +89,40 @@ class UnifiedLocationClient private constructor(context: Context) { val pm = context.packageManager var resolveInfos = pm.queryIntentServices(intent, 0) if (resolveInfos.size > 1) { // Pick own package if possible for (info in resolveInfos) { if (info.serviceInfo.packageName == context.packageName) { intent.setPackage(info.serviceInfo.packageName) Log.d(TAG, "Found more than one active unified service, picked own package " + intent.getPackage()!!) return intent } } // Pick package with matching signature if possible for (info in resolveInfos) { if (pm.checkSignatures(context.packageName, info.serviceInfo.packageName) == SIGNATURE_MATCH) { intent.setPackage(info.serviceInfo.packageName) Log.d(TAG, "Found more than one active unified service, picked related package " + intent.getPackage()!!) return intent // Restrict to self if possible val isSelf: (it: ResolveInfo) -> Boolean = { it.serviceInfo.packageName == context.packageName } if (resolveInfos.size > 1 && resolveInfos.any(isSelf)) { Log.d(TAG, "Found more than one active unified service, restricted to own package " + context.packageName) resolveInfos = resolveInfos.filter(isSelf) } // Restrict to system if single package is system if (resolveInfos.any { (it.serviceInfo?.applicationInfo?.flags ?: 0) and ApplicationInfo.FLAG_SYSTEM > 0 }) { Log.d(TAG, "Found more than one active unified service, restricted to system packages") resolveInfos = resolveInfos.filter { (it.serviceInfo?.applicationInfo?.flags ?: 0) and ApplicationInfo.FLAG_SYSTEM > 0 // Restrict to package with matching signature if possible val isSelfSig: (it: ResolveInfo) -> Boolean = { it.serviceInfo.packageName == context.packageName } if (resolveInfos.size > 1 && resolveInfos.any(isSelfSig)) { Log.d(TAG, "Found more than one active unified service, restricted to related packages") resolveInfos = resolveInfos.filter(isSelfSig) } var highestPriority: ResolveInfo? = null for (info in resolveInfos) { if (highestPriority == null || highestPriority.priority < info.priority) { highestPriority = info // Restrict to system if any package is system val isSystem: (it: ResolveInfo) -> Boolean = { (it.serviceInfo?.applicationInfo?.flags ?: 0) and ApplicationInfo.FLAG_SYSTEM > 0 } if (resolveInfos.size > 1 && resolveInfos.any(isSystem)) { Log.d(TAG, "Found more than one active unified service, restricted to system packages") resolveInfos = resolveInfos.filter(isSystem) } val highestPriority: ResolveInfo? = resolveInfos.maxBy { it.priority } intent.setPackage(highestPriority!!.serviceInfo.packageName) Log.d(TAG, "Found more than one active unified service, picked highest priority " + intent.getPackage()!!) intent.setClassName(highestPriority.serviceInfo.packageName, highestPriority.serviceInfo.name) if (resolveInfos.size > 1) { Log.d(TAG, "Found more than one active unified service, picked highest priority " + intent.component) } return intent } else if (!resolveInfos.isEmpty()) { intent.setPackage(resolveInfos[0].serviceInfo.packageName) Loading @@ -132,17 +134,21 @@ class UnifiedLocationClient private constructor(context: Context) { } @Synchronized private fun updateBinding() { private fun updateBinding(): Boolean { Log.d(TAG, "updateBinding - current: $bound, refs: ${serviceReferenceCount.get()}, reqs: ${requests.size}, avail: $isAvailable") if (!bound && (serviceReferenceCount.get() > 0 || !requests.isEmpty()) && isAvailable) { timer = Timer("unified-client") bound = true bind() return true } else if (bound && serviceReferenceCount.get() == 0 && requests.isEmpty()) { timer!!.cancel() timer = null bound = false unbind() return false } return bound } @Synchronized Loading Loading @@ -173,27 +179,30 @@ class UnifiedLocationClient private constructor(context: Context) { val intent = resolve() ?: return unbind() reconnectCount++ Log.d(TAG, "Binding to $intent") bound = context.bindService(intent, connection, Context.BIND_AUTO_CREATE) } @Synchronized private fun unbind() { try { this.context.get()?.let { it.unbindService(connection) } this.context.get()?.unbindService(connection) } catch (ignored: Exception) { } this.service = null } @Synchronized fun ref() { Log.d(TAG, "ref: ${Exception().stackTrace[1]}") serviceReferenceCount.incrementAndGet() updateBinding() } @Synchronized fun unref() { Log.d(TAG, "unref: ${Exception().stackTrace[1]}") serviceReferenceCount.decrementAndGet() updateBinding() } Loading @@ -210,24 +219,34 @@ class UnifiedLocationClient private constructor(context: Context) { requestLocationUpdates(listener, interval, Integer.MAX_VALUE) } @Synchronized fun requestLocationUpdates(listener: LocationListener, interval: Long, count: Int) { requests.removeAll(requests.filter { it.listener === listener }) requests.add(LocationRequest(listener, interval, count)) coroutineScope.launch { updateServiceInterval() updateBinding() } } @Synchronized fun removeLocationUpdates(listener: LocationListener) { coroutineScope.launch { removeRequests(requests.filter { it.listener === listener }) } } private suspend fun refAndGetService(): UnifiedLocationService = suspendCoroutine { continuation -> refAndGetServiceContinued(continuation) } @Synchronized private fun refAndGetServiceContinued(continuation: Continuation<UnifiedLocationService>) { Log.d(TAG, "ref+get: ${Exception().stackTrace[2]}") serviceReferenceCount.incrementAndGet() waitForServiceContinued(continuation) } private suspend fun waitForService(): UnifiedLocationService = suspendCoroutine { continuation -> waitForServiceContinued(continuation) } @Synchronized private fun waitForServiceContinued(continuation: Continuation<UnifiedLocationService>) { val service = service if (service != null) { continuation.resume(service) Loading @@ -235,7 +254,15 @@ class UnifiedLocationClient private constructor(context: Context) { synchronized(waitingForService) { waitingForService.add(continuation) } timer?.schedule(object : TimerTask() { updateBinding() val timer = timer if (timer == null) { synchronized(waitingForService) { waitingForService.remove(continuation) } continuation.resumeWithException(RuntimeException("No timer, called waitForService when not connected")) } else { timer.schedule(object : TimerTask() { override fun run() { try { continuation.resumeWithException(TimeoutException()) Loading @@ -244,13 +271,13 @@ class UnifiedLocationClient private constructor(context: Context) { } } }, CALL_TIMEOUT) updateBinding() } } } fun <T> configureContinuationTimeout(continuation: Continuation<T>, timeout: Long) { private fun <T> configureContinuationTimeout(continuation: Continuation<T>, timeout: Long) { if (timeout <= 0 || timeout == Long.MAX_VALUE) return timer?.schedule(object : TimerTask() { timer!!.schedule(object : TimerTask() { override fun run() { try { Log.w(TAG, "Timeout reached") Loading @@ -262,17 +289,24 @@ class UnifiedLocationClient private constructor(context: Context) { }, timeout) } fun <T> executeSyncWithTimeout(timeout: Long = CALL_TIMEOUT, action: suspend () -> T): T { private fun <T> executeSyncWithTimeout(timeout: Long = CALL_TIMEOUT, action: suspend () -> T): T { var result: T? = null val latch = CountDownLatch(1) var err: Exception? = null syncThreads.execute { try { runBlocking { result = withTimeout(timeout) { 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() } Loading Loading @@ -302,7 +336,7 @@ class UnifiedLocationClient private constructor(context: Context) { service.getFromLocationNameWithOptions(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale, options, AddressContinuation(continuation)) configureContinuationTimeout(continuation, timeout) } } catch (e: RemoteException) { } catch (e: Exception) { Log.w(TAG, "Failed to request geocode", e) emptyList() } finally { Loading @@ -327,7 +361,7 @@ class UnifiedLocationClient private constructor(context: Context) { suspend fun getLocationBackends(): Array<String> { try { return refAndGetService().locationBackends } catch (e: RemoteException) { } catch (e: Exception) { Log.w(TAG, "Failed to handle request", e) return emptyArray() } finally { Loading @@ -338,7 +372,7 @@ class UnifiedLocationClient private constructor(context: Context) { suspend fun setLocationBackends(backends: Array<String>) { try { refAndGetService().locationBackends = backends } catch (e: RemoteException) { } catch (e: Exception) { Log.w(TAG, "Failed to handle request", e) } finally { unref() Loading Loading @@ -366,6 +400,10 @@ class UnifiedLocationClient private constructor(context: Context) { } } fun getLastLocationSync(timeout: Long = CALL_TIMEOUT): Location? = executeSyncWithTimeout(timeout) { getLastLocation() } suspend fun getLastLocation(): Location? { return try { refAndGetService().lastLocation Loading @@ -388,12 +426,11 @@ class UnifiedLocationClient private constructor(context: Context) { } } @Synchronized private fun removeRequestPendingRemoval() { private suspend fun removeRequestPendingRemoval() { removeRequests(requests.filter { it.needsRemoval }) } private fun removeRequests(removalNeeded: List<LocationRequest>) { private suspend fun removeRequests(removalNeeded: List<LocationRequest>) { if (removalNeeded.isNotEmpty()) { requests.removeAll(removalNeeded) updateServiceInterval() Loading @@ -401,9 +438,7 @@ class UnifiedLocationClient private constructor(context: Context) { } } @Synchronized private fun updateServiceInterval() { if (service == null) return private suspend fun updateServiceInterval() { var interval: Long = Long.MAX_VALUE var requestSingle = false for (request in requests) { Loading @@ -419,11 +454,18 @@ class UnifiedLocationClient private constructor(context: Context) { } else { Log.d(TAG, "Set update interval to $interval") } val service: UnifiedLocationService try { service!!.setUpdateInterval(interval, options) service = waitForService() } catch (e: Exception) { Log.w(TAG, e) return } try { service.setUpdateInterval(interval, options) if (requestSingle) { Log.d(TAG, "Request single update (force update: $forceNextUpdate)") service!!.requestSingleUpdate(options) service.requestSingleUpdate(options) forceNextUpdate = false } } catch (e: DeadObjectException) { Loading @@ -434,7 +476,6 @@ class UnifiedLocationClient private constructor(context: Context) { } } @Synchronized private fun onServiceConnected(name: ComponentName, binder: IBinder) { Log.d(TAG, "Connected to $name") Loading @@ -446,24 +487,40 @@ class UnifiedLocationClient private constructor(context: Context) { continuations.addAll(waitingForService) waitingForService.clear() } for (continuation in continuations) { continuation.resume(service) } coroutineScope.launch { try { Log.d(TAG, "Registering location callback") service.registerLocationCallback(object : LocationCallback.Stub() { override fun onLocationUpdate(location: Location) { for (request in requests) { request.handleLocation(location) coroutineScope.launch { this@UnifiedLocationClient.onLocationUpdate(location) } removeRequestPendingRemoval() } }, options) updateServiceInterval() Log.d(TAG, "Registered location callback") } catch (e: Exception) { Log.w(TAG, "Failed to register location callback", e) } updateServiceInterval() if (continuations.size > 0) { Log.d(TAG, "Resuming ${continuations.size} continuations") } for (continuation in continuations) { try { continuation.resume(service) } catch (e: Exception) { Log.w(TAG, e) } } } } private suspend fun onLocationUpdate(location: Location) { for (request in requests) { request.handleLocation(location) } removeRequestPendingRemoval() } @Synchronized private fun onServiceDisconnected(name: ComponentName) { Loading Loading @@ -531,7 +588,9 @@ class UnifiedLocationClient private constructor(context: Context) { companion object { const val ACTION_UNIFIED_LOCATION_SERVICE = "org.microg.nlp.service.UnifiedLocationService" const val KEY_FORCE_NEXT_UPDATE = "org.microg.nlp.client.FORCE_NEXT_UPDATE" const val PERMISSION_SERVICE_ADMIN = "org.microg.nlp.SERVICE_ADMIN" const val KEY_FORCE_NEXT_UPDATE = "org.microg.nlp.FORCE_NEXT_UPDATE" const val KEY_OP_PACKAGE_NAME = "org.microg.nlp.OP_PACKAGE_NAME" private val TAG = "ULocClient" private var client: UnifiedLocationClient? = null Loading
location-v2/src/main/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ <uses-permission android:name="android.permission.ACCESS_COARSE_UPDATES" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="org.microg.nlp.SERVICE_ADMIN" /> <application> <uses-library android:name="com.android.location.provider" /> Loading
location-v2/src/main/java/org/microg/nlp/location/v2/LocationProvider.java +21 −0 Original line number Diff line number Diff line Loading @@ -18,9 +18,14 @@ import com.android.location.provider.ProviderRequestUnbundled; import org.microg.nlp.client.UnifiedLocationClient; import java.lang.reflect.Field; import java.util.Arrays; import java.util.List; import static android.location.LocationProvider.AVAILABLE; public class LocationProvider extends LocationProviderBase implements UnifiedLocationClient.LocationListener { private static final List<String> EXCLUDED_PACKAGES = Arrays.asList("android", "com.android.location.fused", "com.google.android.gms"); private static final long FASTEST_REFRESH_INTERVAL = 30000; private static final String TAG = "ULocation"; private Context context; Loading @@ -43,6 +48,21 @@ public class LocationProvider extends LocationProviderBase implements UnifiedLoc @Override public void onSetRequest(ProviderRequestUnbundled requests, WorkSource source) { Log.v(TAG, "onSetRequest: " + requests + " by " + source); String opPackageName = null; try { Field namesField = WorkSource.class.getDeclaredField("mNames"); namesField.setAccessible(true); String[] names = (String[]) namesField.get(source); if (names != null) { for (String name : names) { if (!EXCLUDED_PACKAGES.contains(name)) { opPackageName = name; break; } } } } catch (Exception ignored) { } long autoTime = Math.max(requests.getInterval(), FASTEST_REFRESH_INTERVAL); boolean autoUpdate = requests.getReportLocation(); Loading @@ -50,6 +70,7 @@ public class LocationProvider extends LocationProviderBase implements UnifiedLoc Log.v(TAG, "using autoUpdate=" + autoUpdate + " autoTime=" + autoTime); if (autoUpdate) { UnifiedLocationClient.get(context).setOpPackageName(opPackageName); UnifiedLocationClient.get(context).requestLocationUpdates(this, autoTime); } else { UnifiedLocationClient.get(context).removeLocationUpdates(this); Loading
service/build.gradle +3 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,9 @@ dependencies { implementation project(':api') implementation project(':client') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" } afterEvaluate { Loading