Repo Created

This commit is contained in:
Fr4nz D13trich 2025-11-15 17:44:12 +01:00
parent eb305e2886
commit a8c22c65db
4784 changed files with 329907 additions and 2 deletions

View file

@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
dependencies {
api project(':play-services-location')
implementation project(':play-services-base-core')
}
android {
namespace "org.microg.gms.location.base"
compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"
buildFeatures {
buildConfig = true
}
defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
def onlineSourcesString = ""
if (localProperties.get("location.online-sources", "") != "") {
onlineSourcesString = localProperties.get("location.online-sources", "[]")
} else if (localProperties.get("ichnaea.endpoint", "") != "") {
onlineSourcesString = "[{\"id\": \"default\", \"url\": \"${localProperties.get("ichnaea.endpoint", "")}\"},{\"id\": \"custom\", \"import\": true}]"
} else {
onlineSourcesString = "[{\"id\": \"beacondb\", \"name\": \"BeaconDB\", \"url\": \"https://api.beacondb.net/\", \"host\": \"beacondb.net\", \"terms\": \"https://beacondb.net/privacy/\", \"import\": true, \"allowContribute\": true},{\"id\": \"custom\", \"import\": true}]"
}
buildConfigField "java.util.List<org.microg.gms.location.network.OnlineSource>", "ONLINE_SOURCES", "org.microg.gms.location.network.OnlineSourceKt.parseOnlineSources(\"${onlineSourcesString.replaceAll("\"", "\\\\\"")}\")"
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
kotlinOptions {
jvmTarget = 1.8
}
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2023 microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<manifest />

View file

@ -0,0 +1,102 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.location
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import org.microg.gms.location.base.BuildConfig
import org.microg.gms.settings.SettingsContract
private const val PATH_GEOLOCATE = "/v1/geolocate"
private const val PATH_GEOLOCATE_QUERY = "/v1/geolocate?"
private const val PATH_GEOSUBMIT = "/v2/geosubmit"
private const val PATH_GEOSUBMIT_QUERY = "/v2/geosubmit?"
private const val PATH_QUERY_ONLY = "/?"
class LocationSettings(private val context: Context) {
private fun <T> getSettings(vararg projection: String, f: (Cursor) -> T): T =
SettingsContract.getSettings(context, SettingsContract.Location.getContentUri(context), projection, f)
private fun setSettings(v: ContentValues.() -> Unit) = SettingsContract.setSettings(context, SettingsContract.Location.getContentUri(context), v)
var wifiIchnaea: Boolean
get() = getSettings(SettingsContract.Location.WIFI_ICHNAEA) { c -> c.getInt(0) != 0 }
set(value) = setSettings { put(SettingsContract.Location.WIFI_ICHNAEA, value) }
var wifiMoving: Boolean
get() = getSettings(SettingsContract.Location.WIFI_MOVING) { c -> c.getInt(0) != 0 }
set(value) = setSettings { put(SettingsContract.Location.WIFI_MOVING, value) }
var wifiLearning: Boolean
get() = getSettings(SettingsContract.Location.WIFI_LEARNING) { c -> c.getInt(0) != 0 }
set(value) = setSettings { put(SettingsContract.Location.WIFI_LEARNING, value) }
var wifiCaching: Boolean
get() = getSettings(SettingsContract.Location.WIFI_CACHING) { c -> c.getInt(0) != 0 }
set(value) = setSettings { put(SettingsContract.Location.WIFI_CACHING, value) }
var cellIchnaea: Boolean
get() = getSettings(SettingsContract.Location.CELL_ICHNAEA) { c -> c.getInt(0) != 0 }
set(value) = setSettings { put(SettingsContract.Location.CELL_ICHNAEA, value) }
var cellLearning: Boolean
get() = getSettings(SettingsContract.Location.CELL_LEARNING) { c -> c.getInt(0) != 0 }
set(value) = setSettings { put(SettingsContract.Location.CELL_LEARNING, value) }
var cellCaching: Boolean
get() = getSettings(SettingsContract.Location.CELL_CACHING) { c -> c.getInt(0) != 0 }
set(value) = setSettings { put(SettingsContract.Location.CELL_CACHING, value) }
var geocoderNominatim: Boolean
get() = getSettings(SettingsContract.Location.GEOCODER_NOMINATIM) { c -> c.getInt(0) != 0 }
set(value) = setSettings { put(SettingsContract.Location.GEOCODER_NOMINATIM, value) }
var customEndpoint: String?
get() {
try {
var endpoint = getSettings(SettingsContract.Location.ICHNAEA_ENDPOINT) { c -> c.getString(0) }
// This is only temporary as users might have already broken configuration.
// Usually this would be corrected before storing it in settings, see below.
if (endpoint.endsWith(PATH_GEOLOCATE)) {
endpoint = endpoint.substring(0, endpoint.length - PATH_GEOLOCATE.length + 1)
} else if (endpoint.contains(PATH_GEOLOCATE_QUERY)) {
endpoint = endpoint.replace(PATH_GEOLOCATE_QUERY, PATH_QUERY_ONLY)
} else if (endpoint.endsWith(PATH_GEOSUBMIT)) {
endpoint = endpoint.substring(0, endpoint.length - PATH_GEOSUBMIT.length + 1)
} else if (endpoint.contains(PATH_GEOSUBMIT_QUERY)) {
endpoint = endpoint.replace(PATH_GEOSUBMIT_QUERY, PATH_QUERY_ONLY)
}
return endpoint
} catch (e: Exception) {
return null
}
}
set(value) {
val endpoint = if (value == null) {
null
} else if (value.endsWith(PATH_GEOLOCATE)) {
value.substring(0, value.length - PATH_GEOLOCATE.length + 1)
} else if (value.contains(PATH_GEOLOCATE_QUERY)) {
value.replace(PATH_GEOLOCATE_QUERY, PATH_QUERY_ONLY)
} else if (value.endsWith(PATH_GEOSUBMIT)) {
value.substring(0, value.length - PATH_GEOSUBMIT.length + 1)
} else if (value.contains(PATH_GEOSUBMIT_QUERY)) {
value.replace(PATH_GEOSUBMIT_QUERY, PATH_QUERY_ONLY)
} else {
value
}
setSettings { put(SettingsContract.Location.ICHNAEA_ENDPOINT, endpoint) }
}
var onlineSourceId: String?
get() = getSettings(SettingsContract.Location.ONLINE_SOURCE) { c -> c.getString(0) }
set(value) = setSettings { put(SettingsContract.Location.ONLINE_SOURCE, value) }
var ichnaeaContribute: Boolean
get() = getSettings(SettingsContract.Location.ICHNAEA_CONTRIBUTE) { c -> c.getInt(0) != 0 }
set(value) = setSettings { put(SettingsContract.Location.ICHNAEA_CONTRIBUTE, value) }
}

View file

@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.location
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import android.os.SystemClock
import android.text.format.DateUtils
import androidx.core.location.LocationCompat
const val ACTION_NETWORK_LOCATION_SERVICE = "org.microg.gms.location.network.ACTION_NETWORK_LOCATION_SERVICE"
const val EXTRA_LOCATION = "location"
const val EXTRA_ELAPSED_REALTIME = "elapsed_realtime"
const val EXTRA_PENDING_INTENT = "pending_intent"
const val EXTRA_ENABLE = "enable"
const val EXTRA_INTERVAL_MILLIS = "interval"
const val EXTRA_FORCE_NOW = "force_now"
const val EXTRA_LOW_POWER = "low_power"
const val EXTRA_WORK_SOURCE = "work_source"
const val EXTRA_BYPASS = "bypass"
const val ACTION_CONFIGURATION_REQUIRED = "org.microg.gms.location.network.ACTION_CONFIGURATION_REQUIRED"
const val EXTRA_CONFIGURATION = "config"
const val CONFIGURATION_FIELD_ONLINE_SOURCE = "online_source"
const val ACTION_NETWORK_IMPORT_EXPORT = "org.microg.gms.location.network.ACTION_NETWORK_IMPORT_EXPORT"
const val EXTRA_DIRECTION = "direction"
const val DIRECTION_IMPORT = "import"
const val DIRECTION_EXPORT = "export"
const val EXTRA_NAME = "name"
const val NAME_WIFI = "wifi"
const val NAME_CELL = "cell"
const val EXTRA_URI = "uri"
const val EXTRA_MESSENGER = "messenger"
const val EXTRA_REPLY_WHAT = "what"
val Location.elapsedMillis: Long
get() = LocationCompat.getElapsedRealtimeMillis(this)
fun Long.formatRealtime(): CharSequence = if (this <= 0) "n/a" else DateUtils.getRelativeTimeSpanString((this - SystemClock.elapsedRealtime()) + System.currentTimeMillis(), System.currentTimeMillis(), 0)
fun Long.formatDuration(): CharSequence {
if (this == 0L) return "0ms"
if (this > 315360000000L /* ten years */) return "\u221e"
val interval = listOf(1000, 60, 60, 24, Long.MAX_VALUE)
val intervalName = listOf("ms", "s", "m", "h", "d")
var ret = ""
var rem = this
for (i in 0 until interval.size) {
val mod = rem % interval[i]
if (mod != 0L) {
ret = "$mod${intervalName[i]}$ret"
}
rem /= interval[i]
if (mod == 0L && rem == 1L) {
ret = "${interval[i]}${intervalName[i]}$ret"
break
} else if (rem == 0L) {
break
}
}
return ret
}
private var hasNetworkLocationServiceBuiltInFlag: Boolean? = null
fun Context.hasNetworkLocationServiceBuiltIn(): Boolean {
var flag = hasNetworkLocationServiceBuiltInFlag
if (flag == null) {
try {
val serviceIntent = Intent().apply {
action = ACTION_NETWORK_LOCATION_SERVICE
setPackage(packageName)
}
val services = packageManager?.queryIntentServices(serviceIntent, PackageManager.MATCH_DEFAULT_ONLY)
flag = services?.isNotEmpty() ?: false
hasNetworkLocationServiceBuiltInFlag = flag
return flag
} catch (e: Exception) {
hasNetworkLocationServiceBuiltInFlag = false
return false
}
} else {
return flag
}
}

View file

@ -0,0 +1,98 @@
/*
* SPDX-FileCopyrightText: 2024 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.location.network
import android.net.Uri
import android.util.Log
import org.json.JSONArray
import org.json.JSONObject
import org.microg.gms.location.LocationSettings
import org.microg.gms.location.base.BuildConfig
fun parseOnlineSources(string: String): List<OnlineSource> = JSONArray(string).let { array ->
(0 until array.length()).map { parseOnlineSource(array.getJSONObject(it)) }.also { Log.d("Location", "parseOnlineSources: ${it.joinToString()}") }
}
fun parseOnlineSource(json: JSONObject): OnlineSource {
val id = json.getString("id")
val url = json.optString("url").takeIf { it.isNotBlank() }
val host = json.optString("host").takeIf { it.isNotBlank() } ?: runCatching { Uri.parse(url).host }.getOrNull()
val name = json.optString("name").takeIf { it.isNotBlank() } ?: host
return OnlineSource(
id = id,
name = name,
url = url,
host = host,
terms = json.optString("terms").takeIf { it.isNotBlank() }?.let { runCatching { Uri.parse(it) }.getOrNull() },
suggested = json.optBoolean("suggested", false),
import = json.optBoolean("import", false),
allowContribute = json.optBoolean("allowContribute", false),
)
}
data class OnlineSource(
val id: String,
val name: String? = null,
val url: String? = null,
val host: String? = null,
val terms: Uri? = null,
/**
* Show suggested flag
*/
val suggested: Boolean = false,
/**
* If set, automatically import from custom URL if host matches (is the same domain suffix)
*/
val import: Boolean = false,
val allowContribute: Boolean = false,
) {
companion object {
/**
* Entry to allow configuring a custom URL
*/
val ID_CUSTOM = "custom"
/**
* Legacy compatibility
*/
val ID_DEFAULT = "default"
val ALL: List<OnlineSource> = BuildConfig.ONLINE_SOURCES
}
}
val LocationSettings.onlineSource: OnlineSource?
get() {
val id = onlineSourceId
if (id != null) {
val source = OnlineSource.ALL.firstOrNull { it.id == id }
if (source != null) return source
}
val endpoint = customEndpoint
if (endpoint != null) {
val endpointHostSuffix = runCatching { "." + Uri.parse(endpoint).host }.getOrNull()
if (endpointHostSuffix != null) {
for (source in OnlineSource.ALL) {
if (source.import && endpointHostSuffix.endsWith("." + source.host)) {
return source
}
}
}
val customSource = OnlineSource.ALL.firstOrNull { it.id == OnlineSource.ID_CUSTOM }
if (customSource != null && customSource.import) {
return customSource
}
}
if (OnlineSource.ALL.size == 1) return OnlineSource.ALL.single()
return OnlineSource.ALL.firstOrNull { it.id == OnlineSource.ID_DEFAULT }
}
val LocationSettings.effectiveEndpoint: String?
get() {
val source = onlineSource ?: return null
if (source.id == OnlineSource.ID_CUSTOM) return customEndpoint
return source.url
}