Meshtastic-Android/app/src/main/java/com/geeksville/mesh/MainActivity.kt

341 lines
11 KiB
Kotlin
Raw Normal View History

2020-01-22 21:46:41 -08:00
package com.geeksville.mesh
2020-01-20 15:53:22 -08:00
2020-01-21 13:12:01 -08:00
import android.Manifest
import android.accounts.AccountManager
2020-01-24 12:49:27 -08:00
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
2020-02-09 05:52:17 -08:00
import android.content.*
2020-01-21 13:12:01 -08:00
import android.content.pm.PackageManager
2020-01-20 15:53:22 -08:00
import android.os.Bundle
2020-01-23 08:09:50 -08:00
import android.os.IBinder
import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds.Phone
2020-01-20 15:53:22 -08:00
import android.view.Menu
import android.view.MenuItem
2020-01-22 13:02:24 -08:00
import android.widget.Toast
2020-01-24 12:49:27 -08:00
import androidx.appcompat.app.AppCompatActivity
2020-01-21 13:12:01 -08:00
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
2020-01-22 14:48:06 -08:00
import androidx.ui.core.setContent
2020-01-22 14:27:22 -08:00
import com.geeksville.android.Logging
import com.geeksville.mesh.service.*
2020-02-10 10:32:12 -08:00
import com.geeksville.mesh.ui.MeshApp
import com.geeksville.mesh.ui.TextMessage
import com.geeksville.mesh.ui.UIState
2020-02-04 21:23:52 -08:00
import com.geeksville.util.exceptionReporter
2020-02-09 05:52:17 -08:00
import java.nio.charset.Charset
import java.util.*
2020-01-20 15:53:22 -08:00
2020-01-21 09:37:39 -08:00
class MainActivity : AppCompatActivity(), Logging,
ActivityCompat.OnRequestPermissionsResultCallback {
2020-01-20 15:53:22 -08:00
2020-01-21 09:37:39 -08:00
companion object {
const val REQUEST_ENABLE_BT = 10
2020-01-21 13:12:01 -08:00
const val DID_REQUEST_PERM = 11
2020-01-21 09:37:39 -08:00
}
2020-02-09 07:28:24 -08:00
private val utf8 = Charset.forName("UTF-8")
2020-01-24 12:49:27 -08:00
2020-01-23 08:09:50 -08:00
2020-01-22 13:02:24 -08:00
private val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) {
2020-01-21 09:37:39 -08:00
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
2020-01-22 13:02:24 -08:00
bluetoothManager.adapter
2020-01-21 09:37:39 -08:00
}
private fun requestPermission() {
2020-01-22 14:27:22 -08:00
debug("Checking permissions")
2020-01-22 16:45:27 -08:00
val perms = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
2020-01-21 13:12:01 -08:00
Manifest.permission.ACCESS_BACKGROUND_LOCATION,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
2020-01-24 17:47:32 -08:00
Manifest.permission.WAKE_LOCK,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_CONTACTS,
Manifest.permission.GET_ACCOUNTS
2020-01-22 16:45:27 -08:00
)
val missingPerms = perms.filter {
ContextCompat.checkSelfPermission(
this,
it
) != PackageManager.PERMISSION_GRANTED
}
2020-01-21 13:12:01 -08:00
if (missingPerms.isNotEmpty()) {
missingPerms.forEach {
// Permission is not granted
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(this, it)) {
// FIXME
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
}
}
// Ask for all the missing perms
ActivityCompat.requestPermissions(this, missingPerms.toTypedArray(), DID_REQUEST_PERM)
// DID_REQUEST_PERM is an
// app-defined int constant. The callback method gets the
// result of the request.
} else {
// Permission has already been granted
}
}
2020-01-23 08:09:50 -08:00
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
private fun setOwner() {
try {
if (false) {
val SELF_PROJECTION =
arrayOf(Phone._ID, Phone.DISPLAY_NAME, Phone.PHOTO_THUMBNAIL_URI)
val cursor = contentResolver.query(
ContactsContract.Profile.CONTENT_URI,
SELF_PROJECTION,
null,
null,
null
)
if (cursor == null || !cursor.moveToFirst())
error("Can't get owner contact")
else {
info("me: ${cursor.getString(1)}/${cursor.getString(2)}")
}
}
val am = AccountManager.get(this) // "this" references the current Context
val accounts = am.getAccountsByType("com.google")
accounts.forEach {
info("acct ${it.name} ${it.type}")
}
} catch (e: Throwable) {
error("getting owner failed: $e")
}
meshService!!.setOwner("+16508675309", "Kevin Xter", "kx")
}
private fun sendTestPackets() {
2020-02-04 21:23:52 -08:00
exceptionReporter {
val m = meshService!!
// Do some test operations
val testPayload = "hello world".toByteArray()
m.sendData(
"+16508675310",
testPayload,
MeshProtos.Data.Type.SIGNAL_OPAQUE_VALUE
)
m.sendData(
"+16508675310",
testPayload,
MeshProtos.Data.Type.CLEAR_TEXT_VALUE
)
}
}
2020-02-10 07:40:45 -08:00
2020-01-22 14:48:06 -08:00
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
2020-01-25 10:00:57 -08:00
2020-01-22 14:48:06 -08:00
setContent {
2020-02-10 10:32:12 -08:00
MeshApp()
2020-01-21 09:37:39 -08:00
}
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
2020-01-22 16:45:27 -08:00
if (bluetoothAdapter != null) {
2020-01-22 13:02:24 -08:00
bluetoothAdapter!!.takeIf { !it.isEnabled }?.apply {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
}
2020-01-22 16:45:27 -08:00
} else {
2020-01-22 13:02:24 -08:00
Toast.makeText(this, "Error - this app requires bluetooth", Toast.LENGTH_LONG).show()
2020-01-20 15:53:22 -08:00
}
2020-01-21 12:07:03 -08:00
/* Do this better FIXME */
val usetbeam = false
val address = if (usetbeam) "B4:E6:2D:EA:32:B7" else "24:6F:28:96:C9:2A"
2020-02-13 09:25:39 -08:00
RadioInterfaceService.setBondedDeviceAddress(this, null)
2020-01-21 13:12:01 -08:00
requestPermission()
}
2020-02-09 05:52:17 -08:00
override fun onDestroy() {
unregisterMeshReceiver()
super.onDestroy()
}
private var receiverRegistered = false
private fun registerMeshReceiver() {
logAssert(!receiverRegistered)
2020-02-09 05:52:17 -08:00
val filter = IntentFilter()
filter.addAction(MeshService.ACTION_MESH_CONNECTED)
filter.addAction(MeshService.ACTION_NODE_CHANGE)
filter.addAction(MeshService.ACTION_RECEIVED_DATA)
2020-02-09 05:52:17 -08:00
registerReceiver(meshServiceReceiver, filter)
2020-02-09 05:52:17 -08:00
}
private fun unregisterMeshReceiver() {
if (receiverRegistered) {
receiverRegistered = false
unregisterReceiver(meshServiceReceiver)
}
2020-02-09 05:52:17 -08:00
}
/// Called when we gain/lose a connection to our mesh radio
private fun onMeshConnectionChanged(connected: Boolean) {
UIState.isConnected.value = connected
debug("connchange ${UIState.isConnected.value}")
if (connected) {
// everytime the radio reconnects, we slam in our current owner data
setOwner()
}
}
2020-02-09 05:52:17 -08:00
private val meshServiceReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) = exceptionReporter {
debug("Received from mesh service $intent")
when (intent.action) {
MeshService.ACTION_NODE_CHANGE -> {
val info: NodeInfo = intent.getParcelableExtra(EXTRA_NODEINFO)!!
debug("UI nodechange $info")
2020-02-09 05:52:17 -08:00
// We only care about nodes that have user info
info.user?.id?.let {
2020-02-10 10:32:12 -08:00
val newnodes = UIState.nodes.value.toMutableMap()
2020-02-09 07:28:24 -08:00
newnodes[it] = info
2020-02-10 10:32:12 -08:00
UIState.nodes.value = newnodes
2020-02-09 05:52:17 -08:00
}
}
2020-02-09 07:28:24 -08:00
2020-02-09 05:52:17 -08:00
MeshService.ACTION_RECEIVED_DATA -> {
debug("TODO rxdata")
2020-02-09 05:52:17 -08:00
val sender = intent.getStringExtra(EXTRA_SENDER)!!
val payload = intent.getByteArrayExtra(EXTRA_PAYLOAD)!!
2020-02-09 07:28:24 -08:00
val typ = intent.getIntExtra(EXTRA_TYP, -1)
2020-02-09 05:52:17 -08:00
when (typ) {
MeshProtos.Data.Type.CLEAR_TEXT_VALUE -> {
// FIXME - use the real time from the packet
2020-02-10 10:32:12 -08:00
val modded = UIState.messages.value.toMutableList()
2020-02-09 07:28:24 -08:00
modded.add(TextMessage(Date(), sender, payload.toString(utf8)))
2020-02-10 10:32:12 -08:00
UIState.messages.value = modded
2020-02-09 05:52:17 -08:00
}
else -> TODO()
}
}
MeshService.ACTION_MESH_CONNECTED -> {
val connected = intent.getBooleanExtra(EXTRA_CONNECTED, false)
onMeshConnectionChanged(connected)
2020-02-09 05:52:17 -08:00
}
else -> TODO()
}
}
2020-01-20 15:53:22 -08:00
}
2020-02-04 13:24:04 -08:00
private var meshService: IMeshService? = null
private var isBound = false
2020-01-23 08:09:50 -08:00
2020-02-04 13:24:04 -08:00
private var serviceConnection = object : ServiceConnection {
2020-02-09 05:52:17 -08:00
override fun onServiceConnected(name: ComponentName, service: IBinder) = exceptionReporter {
2020-01-25 10:00:57 -08:00
val m = IMeshService.Stub.asInterface(service)
meshService = m
// We don't start listening for packets until after we are connected to the service
registerMeshReceiver()
// We won't receive a notify for the initial state of connection, so we force an update here
onMeshConnectionChanged(m.isConnected)
2020-02-10 15:39:04 -08:00
debug("connected to mesh service, isConnected=${UIState.isConnected.value}")
2020-02-09 07:28:24 -08:00
// make some placeholder nodeinfos
2020-02-10 10:32:12 -08:00
UIState.nodes.value =
m.nodes.toList().map {
it.user?.id!! to it
}.toMap()
2020-01-23 08:09:50 -08:00
}
override fun onServiceDisconnected(name: ComponentName) {
warn("The mesh service has disconnected")
unregisterMeshReceiver()
2020-01-24 12:49:27 -08:00
meshService = null
2020-01-23 08:09:50 -08:00
}
}
private fun bindMeshService() {
debug("Binding to mesh service!")
2020-01-23 08:09:50 -08:00
// we bind using the well known name, to make sure 3rd party apps could also
logAssert(meshService == null)
val intent = MeshService.startService(this)
if (intent != null) {
// ALSO bind so we can use the api
logAssert(bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE))
isBound = true;
2020-02-04 13:24:04 -08:00
}
2020-01-23 08:09:50 -08:00
}
private fun unbindMeshService() {
// If we have received the service, and hence registered with
// it, then now is the time to unregister.
// if we never connected, do nothing
debug("Unbinding from mesh service!")
2020-02-04 13:24:04 -08:00
if (isBound)
unbindService(serviceConnection)
meshService = null
2020-01-23 08:09:50 -08:00
}
override fun onPause() {
unregisterMeshReceiver() // No point in receiving updates while the GUI is gone, we'll get them when the user launches the activity
2020-01-23 08:09:50 -08:00
unbindMeshService()
super.onPause()
}
override fun onResume() {
super.onResume()
bindMeshService()
}
2020-01-20 15:53:22 -08:00
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
2020-01-20 16:13:40 -08:00
return when (item.itemId) {
2020-01-20 15:53:22 -08:00
R.id.action_settings -> true
else -> super.onOptionsItemSelected(item)
}
}
}
2020-01-21 09:37:39 -08:00
2020-02-09 10:18:26 -08:00