UnifiedNlp/ui/src/main/kotlin/org/microg/nlp/ui/BackendDetailsFragment.kt
2020-07-10 19:13:13 +02:00

214 lines
8.8 KiB
Kotlin

/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.nlp.ui
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager.GET_META_DATA
import android.content.res.ColorStateList
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.microg.nlp.client.UnifiedLocationClient
import org.microg.nlp.ui.viewmodel.BackendType.GEOCODER
import org.microg.nlp.ui.viewmodel.BackendType.LOCATION
import org.microg.nlp.ui.databinding.BackendDetailsBinding
import org.microg.nlp.ui.viewmodel.BackendDetailsCallback
import org.microg.nlp.ui.viewmodel.BackendInfo
import org.microg.nlp.ui.viewmodel.BackendType
import java.util.*
class BackendDetailsFragment : Fragment(R.layout.backend_details), BackendDetailsCallback {
fun Double.toStringWithDigits(digits: Int): String {
val s = this.toString()
val i = s.indexOf('.')
if (i <= 0 || s.length - i - 1 < digits) return s
if (digits == 0) return s.substring(0, i)
return s.substring(0, s.indexOf('.') + digits + 1)
}
fun Float.toStringWithDigits(digits: Int): String {
val s = this.toString()
val i = s.indexOf('.')
if (i <= 0 || s.length - i - 1 < digits) return s
if (digits == 0) return s.substring(0, i)
return s.substring(0, s.indexOf('.') + digits + 1)
}
@ColorInt
fun Context.resolveColor(@AttrRes resid: Int): Int? {
val typedValue = TypedValue()
if (!theme.resolveAttribute(resid, typedValue, true)) return null
val colorRes = if (typedValue.resourceId != 0) typedValue.resourceId else typedValue.data
return ContextCompat.getColor(this, colorRes)
}
val switchBarEnabledColor: Int
get() = context?.resolveColor(androidx.appcompat.R.attr.colorControlActivated) ?: Color.RED
val switchBarDisabledColor: Int
get() {
val color = context?.resolveColor(android.R.attr.textColorSecondary) ?: Color.RED
return Color.argb(100, Color.red(color), Color.green(color), Color.blue(color))
}
val switchBarTrackTintColor: ColorStateList
get() {
val color = context?.resolveColor(android.R.attr.textColorPrimaryInverse)
?: Color.RED
val withAlpha = Color.argb(50, Color.red(color), Color.green(color), Color.blue(color))
return ColorStateList(arrayOf(emptyArray<Int>().toIntArray()), arrayOf(withAlpha).toIntArray())
}
private lateinit var binding: BackendDetailsBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = BackendDetailsBinding.inflate(inflater, container, false)
binding.fragment = this
binding.callbacks = this
return binding.root
}
override fun onResume() {
super.onResume()
binding.switchWidget.trackTintList = switchBarTrackTintColor
UnifiedLocationClient[requireContext()].ref()
lifecycleScope.launchWhenStarted { initContent(createBackendInfo()) }
}
override fun onPause() {
super.onPause()
UnifiedLocationClient[requireContext()].unref()
}
private suspend fun initContent(entry: BackendInfo?) {
binding.entry = entry
binding.executePendingBindings()
updateContent(entry)
}
private var updateInProgress = false
private suspend fun updateContent(entry: BackendInfo?) {
if (entry == null) return
if (!entry.loaded.get()) {
entry.fillDetails(requireContext())
entry.loadIntents(requireActivity() as AppCompatActivity)
}
if (entry.type == LOCATION && entry.enabled.get()) {
if (updateInProgress) return
updateInProgress = true
val client = UnifiedLocationClient[requireContext()]
val locationTemp = client.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
val location = when (locationTemp) {
null -> {
delay(500L) // Wait short time to ensure backend was activated
Log.d(TAG, "Location was not available, requesting once")
client.forceNextUpdate = true
client.getSingleLocation()
val secondAttempt = client.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
if (secondAttempt == null) {
Log.d(TAG, "Location still not available, waiting or giving up")
delay(WAIT_FOR_RESULT)
client.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
} else {
secondAttempt
}
}
else -> locationTemp
} ?: return
var locationString = "${location.latitude.toStringWithDigits(6)}, ${location.longitude.toStringWithDigits(6)}"
val address = client.getFromLocation(location.latitude, location.longitude, 1, Locale.getDefault().toString()).singleOrNull()
if (address != null) {
val addressLine = StringBuilder()
var i = 0
addressLine.append(address.getAddressLine(i))
while (addressLine.length < 10 && address.maxAddressLineIndex > i) {
i++
addressLine.append(", ")
addressLine.append(address.getAddressLine(i))
}
locationString = addressLine.toString()
}
updateInProgress = false
binding.lastLocationString = locationString
binding.executePendingBindings()
} else {
Log.d(TAG, "Location is not available for this backend (type: ${entry.type}, enabled ${entry.enabled.get()}")
binding.lastLocationString = ""
binding.executePendingBindings()
}
}
override fun onAboutClicked(entry: BackendInfo?) {
entry?.aboutIntent?.get()?.let { requireContext().startActivity(it) }
}
override fun onConfigureClicked(entry: BackendInfo?) {
entry?.settingsIntent?.get()?.let { requireContext().startActivity(it) }
}
private suspend fun createBackendInfo(): BackendInfo? {
val type = BackendType.values().find { it.name == arguments?.getString("type") }
?: return null
val packageName = arguments?.getString("package") ?: return null
val name = arguments?.getString("name") ?: return null
val serviceInfo = context?.packageManager?.getServiceInfo(ComponentName(packageName, name), GET_META_DATA)
?: return null
val enabledBackends = when (type) {
GEOCODER -> UnifiedLocationClient[requireContext()].getGeocoderBackends()
LOCATION -> UnifiedLocationClient[requireContext()].getLocationBackends()
}
val info = BackendInfo(serviceInfo, type, firstSignatureDigest(requireContext(), packageName))
info.enabled.set(enabledBackends.contains(info.signedComponent) || enabledBackends.contains(info.unsignedComponent))
return info
}
override fun onEnabledChange(entry: BackendInfo?, newValue: Boolean) {
Log.d(TAG, "onEnabledChange: ${entry?.signedComponent} = $newValue")
val activity = requireActivity() as AppCompatActivity
entry?.enabled?.set(newValue)
activity.lifecycleScope.launch {
entry?.updateEnabled(this@BackendDetailsFragment, newValue)
initContent(entry)
}
}
override fun onAppClicked(entry: BackendInfo?) {
if (entry == null) return
val intent = Intent()
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts("package", entry.serviceInfo.packageName, null)
intent.data = uri
requireContext().startActivity(intent)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
handleActivityResult(requestCode, resultCode, data)
}
companion object {
private const val TAG = "USettings"
private const val WAIT_FOR_RESULT = 5000L
}
}