From 4e6d1be9540a4e9129fe83eaa77c9ff260f5ace0 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 12 Feb 2020 15:47:06 -0800 Subject: [PATCH] start mesh service on boot, store device macaddr in prefs --- TODO.md | 2 + app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 11 ++++ .../java/com/geeksville/mesh/MainActivity.kt | 37 ++++--------- .../geeksville/mesh/MeshUtilApplication.kt | 12 +++++ .../mesh/service/BootCompleteReceiver.kt | 9 ++-- .../geeksville/mesh/service/MeshService.kt | 33 +++++++++++- .../mesh/service/RadioInterfaceService.kt | 52 ++++++++++++------- 8 files changed, 105 insertions(+), 53 deletions(-) diff --git a/TODO.md b/TODO.md index d0aab1bce..26774324b 100644 --- a/TODO.md +++ b/TODO.md @@ -1,11 +1,13 @@ # High priority MVP features required for first public alpha +* start bt receive on boot * when a text arrives, move that node info card to the bottom on the window - put the text to the left of the card. with a small arrow/distance/shortname * let the user type texts somewhere * include a background behind our cloud graphics, so redraws work properly * show direction and distance on the nodeinfo cards * show radio config screen, it shows past channels (and the current one) +* use this for preferences? https://developer.android.com/guide/topics/ui/settings/ * do setOwner every time we connect to the radio, use our settings, radio should ignore if unchanged * send location data for devices that don't have a GPS - https://developer.android.com/training/location/change-location-settings * make nodeinfo card not look like ass diff --git a/app/build.gradle b/app/build.gradle index 5f87b8687..e7da61068 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -75,7 +75,7 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.core:core-ktx:1.1.0' + implementation 'androidx.core:core-ktx:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'com.google.android.material:material:1.0.0' testImplementation 'junit:junit:4.12' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a805bd080..327d85170 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,6 +33,9 @@ + + + @@ -108,7 +111,15 @@ + + + + + + + + diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index c0cf3bff4..4cca184d3 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -6,9 +6,7 @@ import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothManager import android.content.* import android.content.pm.PackageManager -import android.os.Build import android.os.Bundle -import android.os.Debug import android.os.IBinder import android.provider.ContactsContract import android.provider.ContactsContract.CommonDataKinds.Phone @@ -25,7 +23,6 @@ import com.geeksville.mesh.ui.MeshApp import com.geeksville.mesh.ui.TextMessage import com.geeksville.mesh.ui.UIState import com.geeksville.util.exceptionReporter -import com.google.firebase.crashlytics.FirebaseCrashlytics import java.nio.charset.Charset import java.util.* @@ -157,11 +154,6 @@ class MainActivity : AppCompatActivity(), Logging, override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - // We default to off in the manifest, FIXME turn on only if user approves - // leave off when running in the debugger - if (false && !Debug.isDebuggerConnected()) - FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true) - setContent { MeshApp() } @@ -177,6 +169,11 @@ class MainActivity : AppCompatActivity(), Logging, Toast.makeText(this, "Error - this app requires bluetooth", Toast.LENGTH_LONG).show() } + /* Do this better FIXME */ + val usetbeam = false + val address = if (usetbeam) "B4:E6:2D:EA:32:B7" else "24:6F:28:96:C9:2A" + RadioInterfaceService.setBondedDeviceAddress(this, address) + requestPermission() } @@ -292,26 +289,12 @@ class MainActivity : AppCompatActivity(), Logging, // we bind using the well known name, to make sure 3rd party apps could also logAssert(meshService == null) - // bind to our service using the same mechanism an external client would use (for testing coverage) - // The following would work for us, but not external users - //val intent = Intent(this, MeshService::class.java) - //intent.action = IMeshService::class.java.name - val intent = Intent() - intent.setClassName("com.geeksville.mesh", "com.geeksville.mesh.service.MeshService") - - // Before binding we want to explicitly create - so the service stays alive forever (so it can keep - // listening for the bluetooth packets arriving from the radio. And when they arrive forward them - // to Signal or whatever. - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - startForegroundService(intent) - } else { - startService(intent) + 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; } - - // ALSO bind so we can use the api - logAssert(bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)) - isBound = true; } private fun unbindMeshService() { diff --git a/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt b/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt index 36fff96a5..264ebbb80 100644 --- a/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt +++ b/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt @@ -1,7 +1,19 @@ package com.geeksville.mesh +import android.os.Debug import com.geeksville.android.GeeksvilleApplication +import com.google.firebase.crashlytics.FirebaseCrashlytics class MeshUtilApplication : GeeksvilleApplication(null, "58e72ccc361883ea502510baa46580e3") { + + override fun onCreate() { + super.onCreate() + + // We default to off in the manifest, FIXME turn on only if user approves + // leave off when running in the debugger + if (false && !Debug.isDebuggerConnected()) + FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true) + + } } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/service/BootCompleteReceiver.kt b/app/src/main/java/com/geeksville/mesh/service/BootCompleteReceiver.kt index 7541c857b..0230e18c3 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BootCompleteReceiver.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BootCompleteReceiver.kt @@ -3,12 +3,13 @@ package com.geeksville.mesh.service import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import com.geeksville.android.Logging -class BootCompleteReceiver : BroadcastReceiver() { +class BootCompleteReceiver : BroadcastReceiver(), Logging { override fun onReceive(mContext: Context, intent: Intent) { - if (intent.action == Intent.ACTION_BOOT_COMPLETED) { - // FIXME - start listening for bluetooth messages from our device - } + // FIXME - start listening for bluetooth messages from our device + info("Received boot complete announcement, starting mesh service") + MeshService.startService(mContext) } } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 026e72d40..32bd6cd0b 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -33,7 +33,7 @@ class RadioNotConnectedException() : Exception("Can't find radio") */ class MeshService : Service(), Logging { - companion object { + companion object : Logging { /// Intents broadcast by MeshService const val ACTION_RECEIVED_DATA = "$prefix.RECEIVED_DATA" @@ -50,7 +50,38 @@ class MeshService : Service(), Logging { /// If the radio hasn't yet joined a mesh (i.e. no nodenum assigned) private const val NODE_NUM_NO_MESH = -1 + /// Helper function to start running our service, returns the intent used to reach it + /// or null if the service could not be started (no bluetooth or no bonded device set) + fun startService(context: Context): Intent? { + if (RadioInterfaceService.getBondedDeviceAddress(context) == null) { + warn("No mesh radio is bonded, not starting service") + return null + } else { + // bind to our service using the same mechanism an external client would use (for testing coverage) + // The following would work for us, but not external users + //val intent = Intent(this, MeshService::class.java) + //intent.action = IMeshService::class.java.name + val intent = Intent() + intent.setClassName( + "com.geeksville.mesh", + "com.geeksville.mesh.service.MeshService" + ) + // Before binding we want to explicitly create - so the service stays alive forever (so it can keep + // listening for the bluetooth packets arriving from the radio. And when they arrive forward them + // to Signal or whatever. + + logAssert( + (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(intent) + } else { + context.startService(intent) + }) != null + ) + + return intent + } + } } /// A mapping of receiver class name to package name - used for explicit broadcasts diff --git a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt index 4c50ec2b5..718ac932e 100644 --- a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt @@ -7,6 +7,7 @@ import android.bluetooth.BluetoothManager import android.content.Context import android.content.Intent import android.os.IBinder +import androidx.core.content.edit import com.geeksville.android.BinaryLogFile import com.geeksville.android.Logging import com.geeksville.concurrent.DeferredExecution @@ -126,6 +127,15 @@ class RadioInterfaceService : Service(), Logging { intent.putExtra(EXTRA_PAYLOAD, payload) context.sendBroadcast(intent) } + + private fun getPrefs(context: Context) = + context.getSharedPreferences("radio-prefs", Context.MODE_PRIVATE) + + /// Return the device we are configured to use, or null for none + fun getBondedDeviceAddress(context: Context) = getPrefs(context).getString("devAddr", null) + + fun setBondedDeviceAddress(context: Context, addr: String) = + getPrefs(context).edit(commit = true) { putString("devAddr", addr) } } private val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) { @@ -244,30 +254,32 @@ class RadioInterfaceService : Service(), Logging { // FIXME, let user GUI select which device we are talking to - // Note: this call does no comms, it just creates the device object (even if the - // device is off/not connected) - val usetbeam = false - val address = if (usetbeam) "B4:E6:2D:EA:32:B7" else "24:6F:28:96:C9:2A" + val address = getBondedDeviceAddress(this) + if (address == null) + error("No bonded mesh radio, can't create service") + else { + // Note: this call does no comms, it just creates the device object (even if the + // device is off/not connected) + val device = bluetoothAdapter?.getRemoteDevice(address) + if (device != null) { + info("Creating radio interface service. device=$address") - val device = bluetoothAdapter?.getRemoteDevice(address) - if (device != null) { - info("Creating radio interface service. device=$address") + // Note this constructor also does no comm + val s = SafeBluetooth(this, device) + safe = s - // Note this constructor also does no comm - val s = SafeBluetooth(this, device) - safe = s + // FIXME, pass in true for autoconnect - so we will autoconnect whenever the radio + // comes in range (even if we made this connect call long ago when we got powered on) + // see https://stackoverflow.com/questions/40156699/which-correct-flag-of-autoconnect-in-connectgatt-of-ble for + // more info + s.asyncConnect(true, ::onConnect, ::onDisconnect) + } else { + error("Bluetooth adapter not found, assuming running on the emulator!") + } - // FIXME, pass in true for autoconnect - so we will autoconnect whenever the radio - // comes in range (even if we made this connect call long ago when we got powered on) - // see https://stackoverflow.com/questions/40156699/which-correct-flag-of-autoconnect-in-connectgatt-of-ble for - // more info - s.asyncConnect(true, ::onConnect, ::onDisconnect) - } else { - error("Bluetooth adapter not found, assuming running on the emulator!") + if (logSends) + sentPacketsLog = BinaryLogFile(this, "sent_log.pb") } - - if (logSends) - sentPacketsLog = BinaryLogFile(this, "sent_log.pb") } override fun onDestroy() {