Merge pull request #388 from meshtastic/1.2-release

1.2.59
This commit is contained in:
Andre Kirchhoff 2022-03-02 17:50:24 -03:00 committed by GitHub
commit 4092fc5c7f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 367 additions and 235 deletions

View file

@ -43,8 +43,8 @@ android {
applicationId "com.geeksville.mesh"
minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works)
targetSdkVersion 30 // 30 can't work until an explicit location permissions dialog is added
versionCode 20258 // format is Mmmss (where M is 1+the numeric major number
versionName "1.2.58"
versionCode 20259 // format is Mmmss (where M is 1+the numeric major number
versionName "1.2.59"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// per https://developer.android.com/studio/write/vector-asset-studio
@ -122,7 +122,7 @@ protobuf {
dependencies {
def room_version = '2.4.1'
def room_version = '2.4.2'
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.4.1'
@ -134,7 +134,7 @@ dependencies {
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
implementation "androidx.room:room-runtime:$room_version"
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "androidx.room:room-compiler:$room_version"

View file

@ -3,6 +3,9 @@ package com.geeksville.mesh
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -15,4 +18,14 @@ object ApplicationModule {
fun provideSharedPreferences(application: Application): SharedPreferences {
return application.getSharedPreferences("ui-prefs", Context.MODE_PRIVATE)
}
@Provides
fun provideProcessLifecycleOwner(): LifecycleOwner {
return ProcessLifecycleOwner.get()
}
@Provides
fun provideProcessLifecycle(processLifecycleOwner: LifecycleOwner): Lifecycle {
return processLifecycleOwner.lifecycle
}
}

View file

@ -0,0 +1,15 @@
package com.geeksville.mesh
import kotlinx.coroutines.Dispatchers
import javax.inject.Inject
/**
* Wrapper around `Dispatchers` to allow for easier testing when using dispatchers
* in injected classes.
*/
class CoroutineDispatchers @Inject constructor() {
val main = Dispatchers.Main
val mainImmediate = Dispatchers.Main.immediate
val default = Dispatchers.Default
val io = Dispatchers.IO
}

View file

@ -4,7 +4,6 @@ import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.content.*
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
@ -40,6 +39,7 @@ import com.geeksville.android.ServiceClient
import com.geeksville.concurrent.handledLaunch
import com.geeksville.mesh.android.*
import com.geeksville.mesh.databinding.ActivityMainBinding
import com.geeksville.mesh.model.BluetoothViewModel
import com.geeksville.mesh.model.ChannelSet
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.UIViewModel
@ -134,11 +134,7 @@ class MainActivity : AppCompatActivity(), Logging,
// Used to schedule a coroutine in the GUI thread
private val mainScope = CoroutineScope(Dispatchers.Main + Job())
private val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) {
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothManager.adapter
}
private val bluetoothViewModel: BluetoothViewModel by viewModels()
val model: UIViewModel by viewModels()
data class TabInfo(val text: String, val icon: Int, val content: Fragment)
@ -187,28 +183,6 @@ class MainActivity : AppCompatActivity(), Logging,
}
}
private val btStateReceiver = BluetoothStateReceiver {
updateBluetoothEnabled()
}
/**
* Don't tell our app we have bluetooth until we have bluetooth _and_ location access
*/
private fun updateBluetoothEnabled() {
var enabled = false // assume failure
if (hasConnectPermission()) {
/// ask the adapter if we have access
bluetoothAdapter?.apply {
enabled = isEnabled
}
} else
errormsg("Still missing needed bluetooth permissions")
debug("Detected our bluetooth access=$enabled")
model.bluetoothEnabled.value = enabled
}
/** Get the minimum permissions our app needs to run correctly
*/
private fun getMinimumPermissions(): List<String> {
@ -381,7 +355,7 @@ class MainActivity : AppCompatActivity(), Logging,
}
}
updateBluetoothEnabled()
bluetoothViewModel.permissionsUpdated()
}
@ -445,12 +419,6 @@ class MainActivity : AppCompatActivity(), Logging,
/// Set theme
setUITheme(prefs)
/// Set initial bluetooth state
updateBluetoothEnabled()
/// We now want to be informed of bluetooth state
registerReceiver(btStateReceiver, btStateReceiver.intentFilter)
/* not yet working
// Configure sign-in to request the user's ID, email address, and basic
// profile. ID and basic profile are included in DEFAULT_SIGN_IN.
@ -569,7 +537,6 @@ class MainActivity : AppCompatActivity(), Logging,
}
override fun onDestroy() {
unregisterReceiver(btStateReceiver)
unregisterMeshReceiver()
mainScope.cancel("Activity going away")
super.onDestroy()
@ -1003,17 +970,17 @@ class MainActivity : AppCompatActivity(), Logging,
override fun onStart() {
super.onStart()
// Ask to start bluetooth if no USB devices are visible
val hasUSB = SerialInterface.findDrivers(this).isNotEmpty()
if (!isInTestLab && !hasUSB) {
if (hasConnectPermission()) {
bluetoothAdapter?.let {
if (!it.isEnabled) {
bluetoothViewModel.enabled.observe(this) { enabled ->
if (!enabled) {
// Ask to start bluetooth if no USB devices are visible
val hasUSB = SerialInterface.findDrivers(this).isNotEmpty()
if (!isInTestLab && !hasUSB) {
if (hasConnectPermission()) {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
}
} else requestPermission()
}
} else requestPermission()
}
}
try {

View file

@ -16,8 +16,8 @@ class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Laz
packetDao.getAllPacket(MAX_ITEMS)
}
suspend fun getAllPacketsInReceiveOrder(): Flow<List<Packet>> = withContext(Dispatchers.IO) {
packetDao.getAllPacketsInReceiveOrder(MAX_ITEMS)
suspend fun getAllPacketsInReceiveOrder(maxItems: Int = MAX_ITEMS): Flow<List<Packet>> = withContext(Dispatchers.IO) {
packetDao.getAllPacketsInReceiveOrder(maxItems)
}
suspend fun insert(packet: Packet) = withContext(Dispatchers.IO) {

View file

@ -16,7 +16,7 @@ data class Packet(@PrimaryKey val uuid: String,
@ColumnInfo(name = "message") val raw_message: String
) {
val proto: MeshProtos.MeshPacket?
val meshPacket: MeshProtos.MeshPacket?
get() {
if (message_type == "packet") {
val builder = MeshProtos.MeshPacket.newBuilder()
@ -28,13 +28,27 @@ data class Packet(@PrimaryKey val uuid: String,
}
return null
}
val nodeInfo: MeshProtos.NodeInfo?
get() {
if (message_type == "NodeInfo") {
val builder = MeshProtos.NodeInfo.newBuilder()
try {
TextFormat.getParser().merge(raw_message, builder)
return builder.build()
} catch (e: IOException) {
}
}
return null
}
val position: MeshProtos.Position?
get() {
return proto?.run {
return meshPacket?.run {
if (hasDecoded() && decoded.portnumValue == Portnums.PortNum.POSITION_APP_VALUE) {
return MeshProtos.Position.parseFrom(decoded.payload)
}
return null
}
} ?: nodeInfo?.position
}
}

View file

@ -0,0 +1,24 @@
package com.geeksville.mesh.model
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.map
import javax.inject.Inject
/**
* Thin view model which adapts the view layer to the `BluetoothRepository`.
*/
@HiltViewModel
class BluetoothViewModel @Inject constructor(
private val bluetoothRepository: BluetoothRepository,
) : ViewModel() {
/**
* Called when permissions have been updated. This causes an explicit refresh of the
* bluetooth state.
*/
fun permissionsUpdated() = bluetoothRepository.refreshState()
val enabled = bluetoothRepository.state.map { it.enabled }.asLiveData()
}

View file

@ -74,10 +74,6 @@ class UIViewModel @Inject constructor(
debug("ViewModel created")
}
fun insertPacket(packet: Packet) = viewModelScope.launch(Dispatchers.IO) {
repository.insert(packet)
}
fun deleteAllPacket() = viewModelScope.launch(Dispatchers.IO) {
repository.deleteAll()
}
@ -229,10 +225,6 @@ class UIViewModel @Inject constructor(
val ownerName = object : MutableLiveData<String>("MrIDE Test") {
}
val bluetoothEnabled = object : MutableLiveData<Boolean>(false) {
}
val provideLocation = object : MutableLiveData<Boolean>(preferences.getBoolean(MyPreferences.provideLocationKey, false)) {
override fun setValue(value: Boolean) {
super.setValue(value)
@ -243,9 +235,6 @@ class UIViewModel @Inject constructor(
}
}
/// If the app was launched because we received a new channel intent, the Url will be here
var requestedChannelUrl: Uri? = null
// clean up all this nasty owner state management FIXME
fun setOwner(s: String? = null) {
@ -283,66 +272,83 @@ class UIViewModel @Inject constructor(
// Capture the current node value while we're still on main thread
val nodes = nodeDB.nodes.value ?: emptyMap()
val positionToPos: (MeshProtos.Position?) -> Position? = { meshPosition ->
meshPosition?.let { Position(it) }.takeIf {
it?.isValid() == true
}
}
writeToUri(file_uri) { writer ->
// Create a map of nodes keyed by their ID
val nodesById = nodes.values.associateBy { it.num }
val nodesById = nodes.values.associateBy { it.num }.toMutableMap()
val nodePositions = mutableMapOf<Int, MeshProtos.Position?>()
writer.appendLine("date,time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx snr,distance,hop limit,payload")
// Packets are ordered by time, we keep most recent position of
// our device in localNodePosition.
var localNodePosition: MeshProtos.Position? = null
val dateFormat = SimpleDateFormat("yyyy-MM-dd,HH:mm:ss", Locale.getDefault())
repository.getAllPacketsInReceiveOrder().first().forEach { packet ->
packet.proto?.let { proto ->
repository.getAllPacketsInReceiveOrder(Int.MAX_VALUE).first().forEach { packet ->
// If we get a NodeInfo packet, use it to update our position data (if valid)
packet.nodeInfo?.let { nodeInfo ->
positionToPos.invoke(nodeInfo.position)?.let { _ ->
nodePositions[nodeInfo.num] = nodeInfo.position
}
}
packet.meshPacket?.let { proto ->
// If the packet contains position data then use it to update, if valid
packet.position?.let { position ->
if (proto.from == myNodeNum) {
localNodePosition = position
} else {
val rxDateTime = dateFormat.format(packet.received_date)
val rxFrom = proto.from.toUInt()
val senderName = nodesById[proto.from]?.user?.longName ?: ""
// sender lat & long
val senderPos = packet.position
?.let { p -> Position(p) }
?.takeIf { p -> p.isValid() }
val senderLat = senderPos?.latitude ?: ""
val senderLong = senderPos?.longitude ?: ""
// rx lat, long, and elevation
val rxPos = localNodePosition
?.let { p -> Position(p) }
?.takeIf { p -> p.isValid() }
val rxLat = rxPos?.latitude ?: ""
val rxLong = rxPos?.longitude ?: ""
val rxAlt = rxPos?.altitude ?: ""
val rxSnr = "%f".format(proto.rxSnr)
// Calculate the distance if both positions are valid
val dist = if (senderPos == null || rxPos == null) {
""
} else {
positionToMeter(
localNodePosition!!,
position
).roundToInt().toString()
}
val hopLimit = proto.hopLimit
val payload = when {
proto.decoded.portnumValue != Portnums.PortNum.TEXT_MESSAGE_APP_VALUE -> "<${proto.decoded.portnum}>"
proto.hasDecoded() -> "\"" + proto.decoded.payload.toStringUtf8()
.replace("\"", "\\\"") + "\""
proto.hasEncrypted() -> "${proto.encrypted.size()} encrypted bytes"
else -> ""
}
// date,time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx snr,distance,hop limit,payload
writer.appendLine("$rxDateTime,$rxFrom,$senderName,$senderLat,$senderLong,$rxLat,$rxLong,$rxAlt,$rxSnr,$dist,$hopLimit,$payload")
positionToPos.invoke(position)?.let { _ ->
nodePositions[proto.from] = position
}
}
// Filter out of our results any packet that doesn't report SNR. This
// is primarily ADMIN_APP.
if (proto.rxSnr > 0.0f) {
val rxDateTime = dateFormat.format(packet.received_date)
val rxFrom = proto.from.toUInt()
val senderName = nodesById[proto.from]?.user?.longName ?: ""
// sender lat & long
val senderPosition = nodePositions[proto.from]
val senderPos = positionToPos.invoke(senderPosition)
val senderLat = senderPos?.latitude ?: ""
val senderLong = senderPos?.longitude ?: ""
// rx lat, long, and elevation
val rxPosition = nodePositions[myNodeNum]
val rxPos = positionToPos.invoke(rxPosition)
val rxLat = rxPos?.latitude ?: ""
val rxLong = rxPos?.longitude ?: ""
val rxAlt = rxPos?.altitude ?: ""
val rxSnr = "%f".format(proto.rxSnr)
// Calculate the distance if both positions are valid
val dist = if (senderPos == null || rxPos == null) {
""
} else {
positionToMeter(
rxPosition!!, // Use rxPosition but only if rxPos was valid
senderPosition!! // Use senderPosition but only if senderPos was valid
).roundToInt().toString()
}
val hopLimit = proto.hopLimit
val payload = when {
proto.decoded.portnumValue != Portnums.PortNum.TEXT_MESSAGE_APP_VALUE -> "<${proto.decoded.portnum}>"
proto.hasDecoded() -> "\"" + proto.decoded.payload.toStringUtf8()
.replace("\"", "\\\"") + "\""
proto.hasEncrypted() -> "${proto.encrypted.size()} encrypted bytes"
else -> ""
}
// date,time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx snr,distance,hop limit,payload
writer.appendLine("$rxDateTime,$rxFrom,$senderName,$senderLat,$senderLong,$rxLat,$rxLong,$rxAlt,$rxSnr,$dist,$hopLimit,$payload")
}
}
}
}

View file

@ -1,4 +1,4 @@
package com.geeksville.mesh.service
package com.geeksville.mesh.repository.bluetooth
import android.bluetooth.BluetoothAdapter
import android.content.BroadcastReceiver
@ -6,29 +6,26 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import com.geeksville.util.exceptionReporter
import javax.inject.Inject
/**
* A helper class to call onChanged when bluetooth is enabled or disabled
*/
class BluetoothStateReceiver(
private val onChanged: (Boolean) -> Unit
class BluetoothBroadcastReceiver @Inject constructor(
private val bluetoothRepository: BluetoothRepository
) : BroadcastReceiver() {
val intentFilter get() = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED) // Can be used for registering
internal val intentFilter get() = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED) // Can be used for registering
override fun onReceive(context: Context, intent: Intent) = exceptionReporter {
if (intent.action == BluetoothAdapter.ACTION_STATE_CHANGED) {
when (intent.bluetoothAdapterState) {
// Simulate a disconnection if the user disables bluetooth entirely
BluetoothAdapter.STATE_OFF -> onChanged(false)
BluetoothAdapter.STATE_ON -> onChanged(true)
BluetoothAdapter.STATE_OFF -> bluetoothRepository.refreshState()
BluetoothAdapter.STATE_ON -> bluetoothRepository.refreshState()
}
}
}
private val Intent.bluetoothAdapterState: Int
get() = getIntExtra(
BluetoothAdapter.EXTRA_STATE,
-1
)
get() = getIntExtra(BluetoothAdapter.EXTRA_STATE,-1)
}

View file

@ -0,0 +1,102 @@
package com.geeksville.mesh.repository.bluetooth
import android.annotation.SuppressLint
import android.app.Application
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.le.BluetoothLeScanner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import com.geeksville.android.Logging
import com.geeksville.mesh.CoroutineDispatchers
import com.geeksville.mesh.android.hasConnectPermission
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Singleton
/**
* Repository responsible for maintaining and updating the state of Bluetooth availability.
*/
@Singleton
class BluetoothRepository @Inject constructor(
private val application: Application,
private val bluetoothAdapterLazy: dagger.Lazy<BluetoothAdapter?>,
private val bluetoothBroadcastReceiverLazy: dagger.Lazy<BluetoothBroadcastReceiver>,
private val dispatchers: CoroutineDispatchers,
private val processLifecycle: Lifecycle,
) : Logging {
private val _state = MutableStateFlow(BluetoothState(
// Assume we have permission until we get our initial state update to prevent premature
// notifications to the user.
hasPermissions = true
))
val state: StateFlow<BluetoothState> = _state.asStateFlow()
init {
processLifecycle.coroutineScope.launch(dispatchers.default) {
updateBluetoothState()
bluetoothBroadcastReceiverLazy.get().let { receiver ->
application.registerReceiver(receiver, receiver.intentFilter)
}
}
}
fun refreshState() {
processLifecycle.coroutineScope.launch(dispatchers.default) {
updateBluetoothState()
}
}
fun getRemoteDevice(address: String): BluetoothDevice? {
return bluetoothAdapterLazy.get()?.getRemoteDevice(address)
}
fun getBluetoothLeScanner(): BluetoothLeScanner? {
return bluetoothAdapterLazy.get()?.bluetoothLeScanner
}
@SuppressLint("MissingPermission")
internal suspend fun updateBluetoothState() {
val newState: BluetoothState = bluetoothAdapterLazy.get()?.takeIf {
application.hasConnectPermission().also { hasPerms ->
if (!hasPerms) errormsg("Still missing needed bluetooth permissions")
}
}?.let { adapter ->
/// ask the adapter if we have access
BluetoothState(
hasPermissions = true,
enabled = adapter.isEnabled,
bondedDevices = createBondedDevicesFlow(adapter),
)
} ?: BluetoothState()
_state.emit(newState)
debug("Detected our bluetooth access=$newState")
}
/**
* Creates a cold Flow used to obtain the set of bonded devices.
*/
@SuppressLint("MissingPermission") // Already checked prior to calling
private suspend fun createBondedDevicesFlow(adapter: BluetoothAdapter): Flow<Set<BluetoothDevice>>? {
return if (adapter.isEnabled) {
flow<Set<BluetoothDevice>> {
withContext(dispatchers.default) {
while (true) {
emit(adapter.bondedDevices)
delay(REFRESH_DELAY_MS)
}
}
}.flowOn(dispatchers.default)
} else {
null
}
}
companion object {
const val REFRESH_DELAY_MS = 1000L
}
}

View file

@ -0,0 +1,26 @@
package com.geeksville.mesh.repository.bluetooth
import android.app.Application
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
interface BluetoothRepositoryModule {
companion object {
@Provides
fun provideBluetoothManager(application: Application): BluetoothManager? {
return application.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?
}
@Provides
fun provideBluetoothAdapter(service: BluetoothManager?): BluetoothAdapter? {
return service?.adapter
}
}
}

View file

@ -0,0 +1,16 @@
package com.geeksville.mesh.repository.bluetooth
import android.bluetooth.BluetoothDevice
import kotlinx.coroutines.flow.Flow
/**
* A snapshot in time of the state of the bluetooth subsystem.
*/
data class BluetoothState(
/** Whether we have adequate permissions to query bluetooth state */
val hasPermissions: Boolean = false,
/** If we have adequate permissions and bluetooth is enabled */
val enabled: Boolean = false,
/** If enabled, a cold flow of the currently bonded devices */
val bondedDevices: Flow<Set<BluetoothDevice>>? = null
)

View file

@ -1560,7 +1560,7 @@ class MeshService : Service(), Logging {
try {
val mi = myNodeInfo
if (mi != null) {
debug("Sending our position/time to=$destNum lat=$lat, lon=$lon, alt=$alt")
debug("Sending our position/time to=$destNum lat=${lat.anonymize}, lon=${lon.anonymize}, alt=$alt")
val position = MeshProtos.Position.newBuilder().also {
it.longitudeI = Position.degI(lon)

View file

@ -2,24 +2,27 @@ package com.geeksville.mesh.service
import android.annotation.SuppressLint
import android.app.Service
import android.companion.CompanionDeviceManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.IBinder
import androidx.core.content.edit
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ServiceLifecycleDispatcher
import androidx.lifecycle.coroutineScope
import com.geeksville.android.BinaryLogFile
import com.geeksville.android.GeeksvilleApplication
import com.geeksville.android.Logging
import com.geeksville.concurrent.handledLaunch
import com.geeksville.mesh.IRadioInterfaceService
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
import com.geeksville.util.anonymize
import com.geeksville.util.ignoreException
import com.geeksville.util.toRemoteExceptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
import javax.inject.Inject
open class RadioNotConnectedException(message: String = "Not connected to radio") :
@ -35,8 +38,18 @@ open class RadioNotConnectedException(message: String = "Not connected to radio"
* Note - this class intentionally dumb. It doesn't understand protobuf framing etc...
* It is designed to be simple so it can be stubbed out with a simulated version as needed.
*/
@AndroidEntryPoint
class RadioInterfaceService : Service(), Logging {
// The following is due to the fact that AIDL prevents us from extending from `LifecycleService`:
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleDispatcher.lifecycle }
private val lifecycleDispatcher: ServiceLifecycleDispatcher by lazy {
ServiceLifecycleDispatcher(lifecycleOwner)
}
@Inject
lateinit var bluetoothRepository: BluetoothRepository
companion object : Logging {
/**
* The RECEIVED_FROMRADIO
@ -104,7 +117,7 @@ class RadioInterfaceService : Service(), Logging {
@SuppressLint("NewApi")
fun getBondedDeviceAddress(context: Context): String? {
// If the user has unpaired our device, treat things as if we don't have one
var address = getDeviceAddress(context)
val address = getDeviceAddress(context)
/// Interfaces can filter addresses to indicate that address is no longer acceptable
if (address != null) {
@ -142,16 +155,6 @@ class RadioInterfaceService : Service(), Logging {
/// true if our interface is currently connected to a device
private var isConnected = false
/**
* If the user turns on bluetooth after we start, make sure to try and reconnected then
*/
private val bluetoothStateReceiver = BluetoothStateReceiver { enabled ->
if (enabled)
startInterface() // If bluetooth just got turned on, try to restart our ble link (which might be bluetooth)
else if (radioIf is BluetoothInterface)
stopInterface() // Was using bluetooth, need to shutdown
}
private fun broadcastConnectionChanged(isConnected: Boolean, isPermanent: Boolean) {
debug("Broadcasting connection=$isConnected")
val intent = Intent(RADIO_CONNECTED_ACTION)
@ -197,19 +200,35 @@ class RadioInterfaceService : Service(), Logging {
override fun onCreate() {
runningService = this
lifecycleDispatcher.onServicePreSuperOnCreate()
super.onCreate()
registerReceiver(bluetoothStateReceiver, bluetoothStateReceiver.intentFilter)
lifecycleOwner.lifecycle.coroutineScope.launch {
bluetoothRepository.state.collect { state ->
if (state.enabled) {
startInterface()
} else {
stopInterface()
}
}
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
lifecycleDispatcher.onServicePreSuperOnStart()
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
unregisterReceiver(bluetoothStateReceiver)
stopInterface()
serviceScope.cancel("Destroying RadioInterface")
runningService = null
lifecycleDispatcher.onServicePreSuperOnDestroy()
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? {
lifecycleDispatcher.onServicePreSuperOnBind()
return binder
}

View file

@ -33,6 +33,7 @@ import com.geeksville.mesh.R
import com.geeksville.mesh.RadioConfigProtos
import com.geeksville.mesh.android.*
import com.geeksville.mesh.databinding.SettingsFragmentBinding
import com.geeksville.mesh.model.BluetoothViewModel
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.service.*
import com.geeksville.mesh.service.SoftwareUpdateService.Companion.ACTION_UPDATE_PROGRESS
@ -447,6 +448,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
private val binding get() = _binding!!
private val scanModel: BTScanModel by activityViewModels()
private val bluetoothViewModel: BluetoothViewModel by activityViewModels()
private val model: UIViewModel by activityViewModels()
// FIXME - move this into a standard GUI helper class
@ -624,7 +626,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
regionAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = regionAdapter
model.bluetoothEnabled.observe(viewLifecycleOwner) {
bluetoothViewModel.enabled.observe(viewLifecycleOwner) {
if (it) binding.changeRadioButton.show()
else binding.changeRadioButton.hide()
}
@ -813,7 +815,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
if (curRadio != null && !MockInterface.addressValid(requireContext(), "")) {
binding.warningNotPaired.visibility = View.GONE
// binding.scanStatusText.text = getString(R.string.current_pair).format(curRadio)
} else if (model.bluetoothEnabled.value == true){
} else if (bluetoothViewModel.enabled.value == true){
binding.warningNotPaired.visibility = View.VISIBLE
binding.scanStatusText.text = getString(R.string.not_paired_yet)
}

View file

@ -48,7 +48,7 @@
<string name="share">Sdílet</string>
<string name="disconnected">Odpojeno</string>
<string name="device_sleeping">Zařízení spí</string>
<string name="connected_count">Pripojeno: %s z %s je online</string>
<string name="connected_count">Pripojeno: %1$s z %2$s je online</string>
<string name="list_of_nodes">Seznam vysílačů v síti</string>
<string name="update_firmware">Aktualizace softwaru</string>
<string name="connected_to">Připojeno k vysílači (%s)</string>

View file

@ -1,142 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="action_settings">Ρυθμίσεις</string>
<string name="channel_name">Όνομα Καναλιού</string>
<string name="channel_options">Επιλογές Καναλιού</string>
<string name="share_button">Κοινή χρήση</string>
<string name="qr_code">Κώδικας QR</string>
<string name="unset">Αναίρεση</string>
<string name="connection_status">Κατάσταση Σύνδεσης</string>
<string name="application_icon">Εικονίδιο εφαρμογής </string>
<string name="unknown_username">Άγνωστο Όνομα Χρήστη</string>
<string name="user_avatar">Avatar Χρήστη</string>
<string name="sample_message">Βρήκα το σημείο, είναι διπλα στη τίγρη. Φοβάμαι λίγο.</string>
<string name="send_text">Αποστολή κειμένου</string>
<string name="warning_not_paired">Δεν έχετε κάνει ακόμη pair μια συσκευή συμβατή με Meshtastic με το τηλέφωνο. Παρακαλώ κάντε pair μια συσκευή και ορίστε το όνομα χρήστη.\n\nΗ εφαρμογή ανοιχτού κώδικα βρίσκεται σε alpha-testing, αν εντοπίσετε προβλήματα παρακαλώ δημοσιεύστε τα στο forum: meshtastic.discourse.group.\n\nΠερισσότερες πληροφορίες στην ιστοσελίδα - www.meshtastic.org.</string>
<string name="username_unset">Όνομα Χρήστη αναιρέθηκε</string>
<string name="your_name">Όνομα</string>
<string name="analytics_okay">Ανώνυμα στατιστικά χρήσης και αναφορές crash.</string>
<string name="looking_for_meshtastic_devices">Αναζήτηση συσκευών Meshtastic …</string>
<string name="requires_bluetooth">Η εφαρμογή απαιτεί bluetooth πρόσβαση. Παρακαλώ παρέχεται σχετική άδεια χρήσης στις ρυθμίσεις του android.</string>
<string name="error_bluetooth">Σφάλμα - η εφαρμογή απαιτεί bluetooth</string>
<string name="starting_pairing">Αρχή pairing</string>
<string name="pairing_failed">Pairing απέτυχε</string>
<string name="url_for_join">Διεύθυνση URL για συμμετοχή σε Meshtastic mesh</string>
<string name="accept">Αποδοχή</string>
<string name="cancel">Ακύρωση</string>
<string name="change_channel">Αλλαγή καναλιού</string>
<string name="are_you_sure_channel">Είστε βέβαιοι ότι θέλετε να αλλάξετε κανάλι? Η επικοινωνία με άλλες συσκευές θα σταματήσεις μέχρι να μοιραστείτε τις ρυθμίσεις του νέου καναλιού.</string>
<string name="new_channel_rcvd">Λήψη URL νέου καναλιού</string>
<string name="do_you_want_switch">Θέλετε να αλλάξετε %s κανάλι?</string>
<string name="map_not_allowed">Έχετε απενεργοποιήσει την ανάλυση δεδομένων. Δυστυχώς ο πάροχος χαρτών μας (mapbox) απαιτεί η ανάλυση δεδομένων να επιτρέπεται στο ‘δωρεάν’ πακέτο. Επομένως έχουμε απενεργοποιήσει το χάρτη.\n\n
Αν επιθυμείτε να δείτε το χάρτη, θα πρέπει να ενεργοποιήσετε την ανάλυση δεδομένων στις Ρυθμίσεις (επίσης - προσωρινά - θα πρέπει να επανεκκινήσετε την εφαρμογή).\n\n
Αν θεωρείτε ότι πρέπει να πληρώνουμε το mapbox (η να αλλάξουμε πάροχο χαρτών), παρακαλώ δημοσιεύστε στο meshtastic.discourse.group</string>
<string name="permission_missing">Λείπει μια απαιτούμενη άδεια, Meshtastic δεν θα λειτοργεί σωστά. Ενεργοποιήστε τις ρυθμίσεις εφαρμογής Android.</string>
<string name="radio_sleeping">Radio σε κατάσταση ύπνου, δεν γίνεται αλλαγή καναλιού</string>
<string name="report_bug">Αναφορά Bug</string>
<string name="report_a_bug">Αναφέρετε ένα bug</string>
<string name="report_bug_text">Είστε σίγουροι ότι θέλετε να αναφέρετε ένα bug? Μετά την αναφορά δημοσιεύστε στο meshtastic.discourse.group ώστε να συνδέσουμε την αναφορά με το συμβάν.</string>
<string name="report">Αναφορά</string>
<string name="select_radio">Επιλογή radio</string>
<string name="current_pair">Έχετε κάνει pair με radio %s</string>
<string name="not_paired_yet">Δεν έχετε κάνει pair με radio ακόμη.</string>
<string name="change_radio">Αλλαγή radio</string>
<string name="please_pair">Παρακαλώ κάντε pair μια συσκευή στις ρυθμίσεις Android.</string>
<string name="pairing_completed">Η διαδικασία pairing ολοκληρώθηκε, εκκίνηση υπηρεσίας</string>
<string name="pairing_failed_try_again">Η διαδικασία pairing απέτυχε, παρακαλώ επιλέξτε πάλι</string>
<string name="location_disabled">Ο εντοπισμός τοποθεσίας είναι απενεργοποιημένος, δε μπορούμε να μοιραστούμε τη θέση σας με το mesh.</string>
<string name="share">Κοινοποίηση</string>
<string name="disconnected">Αποσυνδεδεμένο</string>
<string name="device_sleeping">Συσκευή σε ύπνωση</string>
<string name="connected_count">Συνδεδεμένος: %s από %s online</string>
<string name="connected_count">Συνδεδεμένος: %1$s από %2$s online</string>
<string name="list_of_nodes">Λίστα κόμβων δικτύου</string>
<string name="update_firmware">Αναβάθμιση Firmware</string>
<string name="connected">Συνδεδεμένο στο radio</string>
<string name="connected_to">Συνδεδεμένο στο radio (%s)</string>
<string name="not_connected">Αποσυνδεδεμένο, επιλέξτε radio </string>
<string name="connected_sleeping">Συνδεδεμένο στο radio, αλλά βρίσκεται σε ύπνωση</string>
<string name="update_to">Αναβάθμιση σε %s</string>
<string name="app_too_old">Εφαρμογή πολύ παλαιά</string>
<string name="must_update">Πρέπει να ενημερώσετε την εφαρμογή μέσω Google Play store (ή Github). Είναι πολύ παλαιά ώστε να συνδεθεί με το radio.</string>
<string name="none">Κανένα (απενεργοποιημένο)</string>
<string name="modem_config_short">Μικρή εμβέλεια (αλλά γρήγορο)</string>
<string name="modem_config_medium">Μεσαία εμβέλεια (αλλά γρήγορο)</string>
<string name="modem_config_long">Μεγάλη εμβέλεια (αλλά αργό)</string>
<string name="modem_config_very_long">Πολύ μεγάλη εμβέλεια (αλλά αργό)</string>
<string name="modem_config_unrecognized">ΜΗ ΑΝΑΓΝΩΡΙΣΙΜΟ</string>
<string name="meshtastic_service_notifications">Ειδοποιήσεις Υπηρεσίας Meshtastic</string>
<string name="location_disabled_warning">Πρέπει να ενεργοποιήσετε τις υπηρεσίες εντοπισμού τοποθεσίας στις ρυθμίσεις Android</string>
<string name="about">Σχετικά</string>
<string name="a_list_of_nodes_in_the_mesh">Λίστα κόμβων στο mesh</string>
<string name="text_messages">Μηνύματα</string>
<string name="channel_invalid">Αυτό το κανάλι URL δεν είναι ορθό και δεν μπορεί να χρησιμοποιηθεί</string>
</resources>

View file

@ -45,7 +45,7 @@
<string name="share">Compartir</string>
<string name="disconnected">Desconectado</string>
<string name="device_sleeping">Dispositivo en reposo</string>
<string name="connected_count">Conectado: %s de %s en línea</string>
<string name="connected_count">Conectado: %1$s de %2$s en línea</string>
<string name="list_of_nodes">Una lista de nodos en la red</string>
<string name="update_firmware">Actualizar el firmware</string>
<string name="connected">Conectado a la radio</string>

View file

@ -49,7 +49,7 @@
<string name="share">Partager</string>
<string name="disconnected">Déconnecté</string>
<string name="device_sleeping">Appareil en veille</string>
<string name="connected_count">Connecté: %s sur %s en ligne</string>
<string name="connected_count">Connecté: %1$s sur %2$s en ligne</string>
<string name="list_of_nodes">Une liste de nœuds dans le réseau</string>
<string name="update_firmware">Mise à jour du Firmware</string>
<string name="connected">Connecté à une radio</string>

View file

@ -46,7 +46,7 @@
<string name="share">Pataje</string>
<string name="disconnected">Dekonekte</string>
<string name="device_sleeping">Aparèy ap dòmi</string>
<string name="connected_count">Konekte: %s nan %s disponib</string>
<string name="connected_count">Konekte: %1$s nan %2$s disponib</string>
<string name="list_of_nodes">Yon lis ne elektwonik nan rezo a</string>
<string name="update_firmware">Mete ajou mikrolojisyèl</string>
<string name="connected">Konekte ak radyo</string>

View file

@ -47,7 +47,7 @@
<string name="share">Megosztás</string>
<string name="disconnected">Szétkapcsolva</string>
<string name="device_sleeping">Az eszköz alszik</string>
<string name="connected_count">Kapcsolódva: %s a %s-ból(ből) elérhető</string>
<string name="connected_count">Kapcsolódva: %1$s a %2$s-ból(ből) elérhető</string>
<string name="list_of_nodes">Hálózati állomások listája</string>
<string name="update_firmware">Firmware frissítés</string>
<string name="connected">Kapcsolódva a rádióhoz</string>

View file

@ -48,7 +48,7 @@ mapboxの有償プランまたは代替地図プロバイダを検討さ
<string name="share">シェア</string>
<string name="disconnected">切断</string>
<string name="device_sleeping">スリープ</string>
<string name="connected_count">接続済み:%s人オンライン%s人中</string>
<string name="connected_count">接続済み:%1$s人オンライン%2$s人中</string>
<string name="list_of_nodes">ネットワーク内のノードリスト</string>
<string name="update_firmware">ファームウェアアップデート</string>
<string name="connected_to">Meshtasticデバイスに接続しました。(%s)</string>

View file

@ -46,7 +46,7 @@
<string name="share">공유</string>
<string name="disconnected">연결 해제</string>
<string name="device_sleeping">장치 잠자기</string>
<string name="connected_count">연결: %s 온라인( 전체 %s)</string>
<string name="connected_count">연결: %1$s 온라인( 전체 %2$s)</string>
<string name="list_of_nodes">네트워크안은 모든 노드의 목록</string>
<string name="update_firmware">펌웨어 업데이트</string>
<string name="connected">라디오로 연결됨</string>

View file

@ -48,7 +48,7 @@
<string name="share">Deel</string>
<string name="disconnected">Niet verbonden</string>
<string name="device_sleeping">Apparaat in slaapstand</string>
<string name="connected_count">Verbonden: %s van %s online</string>
<string name="connected_count">Verbonden: %1$s van %2$s online</string>
<string name="list_of_nodes">Een lijst van de aansluitpunten in het netwerk</string>
<string name="update_firmware">Programma Updaten</string>
<string name="connected">Verbonden met een radio</string>

View file

@ -48,7 +48,7 @@
<string name="share">Del</string>
<string name="disconnected">Frakoblet</string>
<string name="device_sleeping">Enhet sover</string>
<string name="connected_count">Tilkoblet: %s av %s på nett</string>
<string name="connected_count">Tilkoblet: %1$s av %2$s på nett</string>
<string name="list_of_nodes">En liste over noder i nettverket</string>
<string name="update_firmware">Oppdater Firmware</string>
<string name="connected">Tilkoblet radio</string>

View file

@ -53,7 +53,7 @@
<string name="share">Udostępnij</string>
<string name="disconnected">Rozłączone</string>
<string name="device_sleeping">Urządzenie uśpione.</string>
<string name="connected_count">Połączono: %s of %s online</string>
<string name="connected_count">Połączono: %1$s of %2$s online</string>
<string name="list_of_nodes">Lista użytkowników w sieci</string>
<string name="update_firmware">Aktualizuj oprogramowanie.</string>
<string name="connected">Połączony z urządzeniem</string>

View file

@ -48,7 +48,7 @@
<string name="share">Compartilhar</string>
<string name="disconnected">Desconectado</string>
<string name="device_sleeping">Dispositivo em suspensão (sleep)</string>
<string name="connected_count">Conectado: %s de %s online</string>
<string name="connected_count">Conectado: %1$s de %2$s online</string>
<string name="list_of_nodes">Lista de dispositivos na rede</string>
<string name="update_firmware">Atualizar Firmware</string>
<string name="connected">Conectado ao rádio</string>

View file

@ -47,7 +47,7 @@
<string name="share">Partilha</string>
<string name="disconnected">Desconectado</string>
<string name="device_sleeping">Dispositivo a dormir</string>
<string name="connected_count">Conectado: %s de %s online</string>
<string name="connected_count">Conectado: %1$s de %2$s online</string>
<string name="list_of_nodes">Lista de nós na rede</string>
<string name="update_firmware">Atualizar Firmware</string>
<string name="connected">Conectado ao rádio</string>

View file

@ -48,7 +48,7 @@
<string name="share">Distribuie</string>
<string name="disconnected">Deconectat</string>
<string name="device_sleeping">Dispozitiv în sleep mode</string>
<string name="connected_count">Connectat: %s din %s online</string>
<string name="connected_count">Connectat: %1$s din %2$s online</string>
<string name="list_of_nodes">O lista cu nodurile din rețea</string>
<string name="update_firmware">Updateaza firmware-ul</string>
<string name="connected">Connectat la dispozitiv</string>

View file

@ -48,7 +48,7 @@
<string name="share">Zdieľať</string>
<string name="disconnected">Odpojené</string>
<string name="device_sleeping">Vysielač uspatý</string>
<string name="connected_count">Pripojený: %s z %s je online</string>
<string name="connected_count">Pripojený: %1$s z %2$s je online</string>
<string name="list_of_nodes">Zoznam vysielačov v sieti</string>
<string name="update_firmware">Aktualizácia firmvéru</string>
<string name="connected">Pripojené k vysielaču</string>

View file

@ -46,7 +46,7 @@
<string name="share">Deliti</string>
<string name="disconnected">Prekinjeno</string>
<string name="device_sleeping">Naprava je v "spanju"</string>
<string name="connected_count">Povezano: %s od %s je na mreži</string>
<string name="connected_count">Povezano: %1$s od %2$s je na mreži</string>
<string name="list_of_nodes">Seznam vozlišč v omrežju</string>
<string name="update_firmware">Posodobite vdelano programsko opremo</string>
<string name="connected">Povezana z radiem</string>

View file

@ -48,7 +48,7 @@
<string name="share">Paylaş</string>
<string name="disconnected">Bağlantı sonlandı</string>
<string name="device_sleeping">Cihaz uyku durumunda</string>
<string name="connected_count">Bağlandı: %s / %s online</string>
<string name="connected_count">Bağlandı: %1$s / %2$s online</string>
<string name="list_of_nodes">Ağdaki node listesi</string>
<string name="update_firmware">Yazılım güncelle</string>
<string name="connected">Radyoya bağlandı</string>

View file

@ -48,7 +48,7 @@
<string name="share">分享</string>
<string name="disconnected">断开连接</string>
<string name="device_sleeping">设备休眠中</string>
<string name="connected_count">连接: %s 中 %s 在线</string>
<string name="connected_count">连接: %1$s 中 %2$s 在线</string>
<string name="list_of_nodes">网络中节点列表</string>
<string name="update_firmware">更新固件</string>
<string name="connected">连接设备</string>

View file

@ -52,7 +52,7 @@
<string name="share">Share</string>
<string name="disconnected">Disconnected</string>
<string name="device_sleeping">Device sleeping</string>
<string name="connected_count">Connected: %s of %s online</string>
<string name="connected_count">Connected: %1$s of %2$s online</string>
<string name="list_of_nodes">A list of nodes in the network</string>
<string name="update_firmware">Update Firmware</string>
<string name="connected">Connected to radio</string>

View file

@ -2,7 +2,7 @@
buildscript {
ext.kotlin_version = '1.6.10'
ext.coroutines_version = "1.5.2"
ext.coroutines_version = "1.6.0"
ext.hilt_version = '2.40.5'
repositories {
@ -10,7 +10,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.1'
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"