Repo Created
This commit is contained in:
parent
eb305e2886
commit
a8c22c65db
4784 changed files with 329907 additions and 2 deletions
51
play-services-location/core/base/build.gradle
Normal file
51
play-services-location/core/base/build.gradle
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: 2023 microG Project Team
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<manifest />
|
||||
|
|
@ -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) }
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue