From 102ed1ddde1b6563d5d41900f79aafce473697a0 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 28 Feb 2020 12:37:15 -0800 Subject: [PATCH 1/5] Update todo list while sitting in airplane --- TODO.md | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/TODO.md b/TODO.md index 58e4ebb7..a22cfaa5 100644 --- a/TODO.md +++ b/TODO.md @@ -1,14 +1,21 @@ # High priority -MVP features required for first public alpha +Work items for soon alpha builds +* change info() log strings to debug() * have the foreground service's notification show a summary of network status, add (individually maskable) notifications for received texts or new positions * show offline nodes as greyed out * show time since last contact on the node info card * show pointer arrow on the outside of the user icons, always pointing towoards them * fix app icon in title bar * show direction on the nodeinfo cards -* make hackaday page -* test using firebase testlab +* take video of the app +* make channel button look like a button +* generate real channel QR codes +* register app link for our URLs https://developer.android.com/studio/write/app-link-indexing.html +* let users change & share channels (but no saving them yet) +* treat macaddrs as the unique id, not the app layer user id +* Have play store entry ask users to report if their android version is too old to allow install +* only publish gps positions once every 5 mins while we are connected to our radio _and_ someone else is in the mesh # Medium priority Features for future builds @@ -17,25 +24,16 @@ Features for future builds * describe user experience: devices always point to each other and show distance, you can send texts between nodes the channel is encrypted, you can share the the channel key with others by qr code or by sharing a special link * be smarter about sharing GPS location with the device (to save power), integrate with new network scheduler -* take video of the app -* make a working currently vs not working list -* make channel button look like a button -* generate real channel QR codes -* let users change & share channels (but no saving them yet) -* stop scan when we start the service -* set the radio by using the service +* stop scan when we start the service - I think this is done, but check +* set the radio macaddr by using the service (not by slamming bytes into the Preferences) * startforegroundservice only if we have a valid radio -* if no radio is selected, launch app on the radio select screen * when we select a new radio, restart the service * show bt scan progress centered and towards the bottom of the screen -* treat macaddrs as the unique id, not the app layer user id * 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 * use this for preferences? https://developer.android.com/guide/topics/ui/settings/ * at connect we might receive messages before finished downloading the nodeinfo. In that case, process those messages later -* connect to bluetooth device automatically using minimum power - start looking at phone boot * test with oldest compatible android in emulator (see below for testing with hardware) -* tell various vendors & post in forums * add play store link with https://developers.google.com/analytics/devguides/collection/android/v4/campaigns#google-play-url-builder and the play icon # Signal alpha release @@ -64,7 +62,6 @@ rules at the BluetoothDevice level. Either make SafeBluetooth lock at the devic * show user icons in chat * keep past messages in db, one db per channel * spend some quality power consumption tuning with https://developer.android.com/studio/profile/energy-profiler and https://developer.android.com/topic/performance/power/battery-historian -* only publish gps positions once every 5 mins while we are connected to our radio _and_ someone else is in the mesh * Do PRIORITY_BALANCED_POWER_ACCURACY for our gps updates when no one in the mesh is nearer than 200 meters * fix slow rendering warnings in play console * use google signin to get user name @@ -72,11 +69,9 @@ rules at the BluetoothDevice level. Either make SafeBluetooth lock at the devic * let user pick/specify a name through ways other than google signin (for the privacy concerned, or devices without Play API) * make my android app show mesh state * show qr code for each channel https://medium.com/@aanandshekharroy/generate-barcode-in-android-app-using-zxing-64c076a5d83a -* register app link for our URLs https://developer.android.com/studio/write/app-link-indexing.html * let user change radio params and share radio join info via QR code or text message (use an encoded app specific URL - to autoprompt for app installation as needed) * test with an oldish android release using real hardware * stop using a foreground service -* change info() log strings to debug() * use platform theme (dark or light) * remove mixpanel analytics * require user auth to pair with the device (i.e. press button on device to allow a new phone to pair with it). @@ -155,4 +150,7 @@ Don't leave device discoverable. Don't let unpaired users do things with device * record analytics events when radio connects/disconnects, include # of nodes in mesh * make a boot screen explaining this is an early alpha, tell user to go to settings if they have a radio, otherwise go to website * when we connect to radio, distances to nodes in the chat log should automatically redraw -* add screenshots and text to play store entry \ No newline at end of file +* add screenshots and text to play store entry +* if no radio is selected, launch app on the radio select screen +* connect to bluetooth device automatically using minimum power - start looking at phone boot +* tell various vendors & post in forums \ No newline at end of file From f4dad37914f35cc26d3698bb1e7ba13b79ddeb1d Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 28 Feb 2020 13:53:16 -0800 Subject: [PATCH 2/5] WIP in airplane - make notifications more useful --- TODO.md | 10 +- .../geeksville/mesh/service/MeshService.kt | 105 +++++++++++------- 2 files changed, 72 insertions(+), 43 deletions(-) diff --git a/TODO.md b/TODO.md index a22cfaa5..7bc4d8c1 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,9 @@ # High priority Work items for soon alpha builds -* change info() log strings to debug() -* have the foreground service's notification show a summary of network status, add (individually maskable) notifications for received texts or new positions +* fix notification setSmallIcon parameter +* have the foreground service's notification show a summary of network status "connected/disconnected, 5 of 6 nodes, nearest: kevin 5km", +* have notification (individually maskable) notifications for received texts - use file:///home/kevinh/packages/android-sdk-linux/docs/reference/android/support/v4/app/NotificationCompat.BigTextStyle.html * show offline nodes as greyed out * show time since last contact on the node info card * show pointer arrow on the outside of the user icons, always pointing towoards them @@ -54,6 +55,8 @@ Do this "Signal app compatible" release relatively soon after the alpha release # Medium priority Things for the betaish period. +* Use setLargeIcon to show user icons in the notification: file:///home/kevinh/packages/android-sdk-linux/docs/design/patterns/notifications.html +* Our notification about messages should use VISIBLITY_PRIVATE + setPublicVersion per file:///home/kevinh/packages/android-sdk-linux/docs/guide/topics/ui/notifiers/notifications.html * let users save old channels * make sw update work while node is connected to mesh - the problem is there are _two_ instances of SafeBluetooth at that moment and therefore we violate undocumented android mutex rules at the BluetoothDevice level. Either make SafeBluetooth lock at the device level or (not as good) make SafeBluetooth a singleton. @@ -153,4 +156,5 @@ Don't leave device discoverable. Don't let unpaired users do things with device * add screenshots and text to play store entry * if no radio is selected, launch app on the radio select screen * connect to bluetooth device automatically using minimum power - start looking at phone boot -* tell various vendors & post in forums \ No newline at end of file +* tell various vendors & post in forums +* change info() log strings to debug() 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 6eaa9859..38d1e5ed 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -1,10 +1,7 @@ package com.geeksville.mesh.service import android.annotation.SuppressLint -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.Service +import android.app.* import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -273,52 +270,78 @@ class MeshService : Service(), Logging { return channelId } - private fun startForeground() { + private val notifyId = 101 + val notificationManager: NotificationManager by lazy() { + getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + } - // val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - val channelId = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createNotificationChannel() - } else { - // If earlier version channel ID is not used - // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) - "" - } + val channelId = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createNotificationChannel() + } else { + // If earlier version channel ID is not used + // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) + "" + } - val notificationBuilder = NotificationCompat.Builder(this, channelId) - val notification = notificationBuilder.setOngoing(true) + var mainAppIntent = Intent(this, MainActivity::class.java) + val openAppIntent = PendingIntent.getActivity(this, 0, mainAppIntent, 0) + val notificationBuilder = NotificationCompat.Builder(this, channelId) + + val summaryString + get() = if (!isConnected) + "No radio connected" + else + "Connected $numOnlineNodes / $numNodes online" + + override fun toString() = summaryString + + /** + * Generate a new version of our notification - reflecting current app state + */ + private fun createNotification(): Notification { + val recentMessage = "FIXME a recent text message" + + val shortContent = "FIXME Include nearest node or text message info" + + val builder = notificationBuilder.setOngoing(true) .setPriority(PRIORITY_MIN) - .setCategory(Notification.CATEGORY_SERVICE) + .setCategory(if (recentMessage != null) Notification.CATEGORY_SERVICE else Notification.CATEGORY_MESSAGE) .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) - //.setContentTitle("Meshtastic") // leave this off for now so our notification looks smaller - //.setContentText("Listening for mesh...") - .build() - startForeground(101, notification) + .setContentTitle("Meshtastic: $summaryString") // leave this off for now so our notification looks smaller + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setContentIntent(openAppIntent) + + if(shortContent != null) + builder.setContentText(shortContent) + + if (recentMessage != null) + builder.setStyle( + NotificationCompat.BigTextStyle() + .bigText(recentMessage) + ) + + return builder.build() + } + + /** + * Update our notification with latest data + */ + private fun updateNotification() { + notificationManager.notify(notifyId, createNotification()) + } + + /** + * tell android not to kill us + */ + private fun startForeground() { + startForeground(notifyId, createNotification()) } override fun onCreate() { super.onCreate() info("Creating mesh service") - - /* - // This intent will be used if the user clicks on the item in the status bar - val notificationIntent = Intent(this, MainActivity::class.java) - val pendingIntent = PendingIntent.getActivity( - this, 0, - notificationIntent, 0 - ) - - val notification: Notification = NotificationCompat.Builder(this) - .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) - .setContentTitle("Meshtastic") - .setContentText("Listening for mesh...") - .setContentIntent(pendingIntent).build() - - // We are required to call this within a few seconds of create - startForeground(1337, notification) - - */ startForeground() // we listen for messages from the radio receiver _before_ trying to create the service @@ -654,6 +677,8 @@ class MeshService : Service(), Logging { DataPair("num_online", numOnlineNodes) ) } + + updateNotification() } /** From 51949166c604580024146a9133379b9aad94ce7d Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 28 Feb 2020 14:07:04 -0800 Subject: [PATCH 3/5] show recent texts in the notification --- .../geeksville/mesh/service/MeshService.kt | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) 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 38d1e5ed..c6b5cbb8 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -84,6 +84,9 @@ class MeshService : Service(), Logging { return intent } } + + /// A model object for a Text message + data class TextMessage(val fromId: String, val text: String) } /// A mapping of receiver class name to package name - used for explicit broadcasts @@ -284,9 +287,12 @@ class MeshService : Service(), Logging { "" } - var mainAppIntent = Intent(this, MainActivity::class.java) - val openAppIntent = PendingIntent.getActivity(this, 0, mainAppIntent, 0) - val notificationBuilder = NotificationCompat.Builder(this, channelId) + private val mainAppIntent = Intent(this, MainActivity::class.java) + private val openAppIntent = PendingIntent.getActivity(this, 0, mainAppIntent, 0) + private val notificationBuilder = NotificationCompat.Builder(this, channelId) + + /// A text message that has a arrived since the last notification update + private var recentReceivedText: TextMessage? = null val summaryString get() = if (!isConnected) @@ -300,13 +306,11 @@ class MeshService : Service(), Logging { * Generate a new version of our notification - reflecting current app state */ private fun createNotification(): Notification { - val recentMessage = "FIXME a recent text message" - val shortContent = "FIXME Include nearest node or text message info" val builder = notificationBuilder.setOngoing(true) .setPriority(PRIORITY_MIN) - .setCategory(if (recentMessage != null) Notification.CATEGORY_SERVICE else Notification.CATEGORY_MESSAGE) + .setCategory(if (recentReceivedText != null) Notification.CATEGORY_SERVICE else Notification.CATEGORY_MESSAGE) .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) .setContentTitle("Meshtastic: $summaryString") // leave this off for now so our notification looks smaller .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) @@ -315,11 +319,15 @@ class MeshService : Service(), Logging { if(shortContent != null) builder.setContentText(shortContent) - if (recentMessage != null) + // If a text message arrived include it with our notification + recentReceivedText?.let { msg -> + builder.setContentText("Message from ${msg.fromId}") + builder.setStyle( NotificationCompat.BigTextStyle() - .bigText(recentMessage) + .bigText(msg.text) ) + } return builder.build() } @@ -495,11 +503,11 @@ class MeshService : Service(), Logging { when (data.typValue) { MeshProtos.Data.Type.CLEAR_TEXT_VALUE -> { - debug( - "FIXME - don't long this: Received CLEAR_TEXT from $fromString: ${bytes.toString( - Charset.forName("UTF-8") - )}" - ) + val text = bytes.toString(Charset.forName("UTF-8")) + + debug("Received CLEAR_TEXT from $fromString") + + recentReceivedText = TextMessage(fromString, text) forwardData() } From 3f987136b20493e95c37e7067efdd6676d73a1dc Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 28 Feb 2020 14:11:42 -0800 Subject: [PATCH 4/5] show recent texts in notification --- .../main/java/com/geeksville/mesh/service/MeshService.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 c6b5cbb8..5f633d0a 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -306,7 +306,6 @@ class MeshService : Service(), Logging { * Generate a new version of our notification - reflecting current app state */ private fun createNotification(): Notification { - val shortContent = "FIXME Include nearest node or text message info" val builder = notificationBuilder.setOngoing(true) .setPriority(PRIORITY_MIN) @@ -316,8 +315,8 @@ class MeshService : Service(), Logging { .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setContentIntent(openAppIntent) - if(shortContent != null) - builder.setContentText(shortContent) + // FIXME, show information about the nearest node + // if(shortContent != null) builder.setContentText(shortContent) // If a text message arrived include it with our notification recentReceivedText?.let { msg -> @@ -508,6 +507,7 @@ class MeshService : Service(), Logging { debug("Received CLEAR_TEXT from $fromString") recentReceivedText = TextMessage(fromString, text) + updateNotification() forwardData() } @@ -642,6 +642,8 @@ class MeshService : Service(), Logging { /// If we just changed our nodedb, we might want to do somethings private fun onNodeDBChanged() { + updateNotification() + // we don't ask for GPS locations from android if our device has a built in GPS if (!myNodeInfo!!.hasGPS) { // If we have at least one other person in the mesh, send our GPS position otherwise stop listening to GPS From 022d542b7c8575b91489c5ada04be8f068adb099 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 28 Feb 2020 20:09:00 -0800 Subject: [PATCH 5/5] new notification status stuff seems to work --- TODO.md | 7 ++++--- .../geeksville/mesh/service/MeshService.kt | 21 +++++++++++-------- .../com/geeksville/mesh/ui/BTScanScreen.kt | 4 +++- .../ic_baseline_settings_input_antenna_24.xml | 10 +++++++++ 4 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 app/src/main/res/drawable/ic_baseline_settings_input_antenna_24.xml diff --git a/TODO.md b/TODO.md index 7bc4d8c1..8e680725 100644 --- a/TODO.md +++ b/TODO.md @@ -1,9 +1,7 @@ # High priority Work items for soon alpha builds -* fix notification setSmallIcon parameter -* have the foreground service's notification show a summary of network status "connected/disconnected, 5 of 6 nodes, nearest: kevin 5km", -* have notification (individually maskable) notifications for received texts - use file:///home/kevinh/packages/android-sdk-linux/docs/reference/android/support/v4/app/NotificationCompat.BigTextStyle.html +* run services in sim mode on emulator * show offline nodes as greyed out * show time since last contact on the node info card * show pointer arrow on the outside of the user icons, always pointing towoards them @@ -21,6 +19,7 @@ Work items for soon alpha builds # Medium priority Features for future builds +* fix notification setSmallIcon parameter - change it to use the meshtastic icon * ditch compose and use https://github.com/zsmb13/MaterialDrawerKt + https://github.com/Kotlin/anko/wiki/Anko-Layouts? * describe user experience: devices always point to each other and show distance, you can send texts between nodes the channel is encrypted, you can share the the channel key with others by qr code or by sharing a special link @@ -158,3 +157,5 @@ Don't leave device discoverable. Don't let unpaired users do things with device * connect to bluetooth device automatically using minimum power - start looking at phone boot * tell various vendors & post in forums * change info() log strings to debug() +* have the foreground service's notification show a summary of network status "connected/disconnected, 5 of 6 nodes, nearest: kevin 5km", +* have notification (individually maskable) notifications for received texts - use file:///home/kevinh/packages/android-sdk-linux/docs/reference/android/support/v4/app/NotificationCompat.BigTextStyle.html 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 5f633d0a..1717a6af 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -268,8 +268,7 @@ class MeshService : Service(), Logging { chan.lightColor = Color.BLUE chan.importance = NotificationManager.IMPORTANCE_NONE chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE - val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - service.createNotificationChannel(chan) + notificationManager.createNotificationChannel(chan) return channelId } @@ -278,7 +277,8 @@ class MeshService : Service(), Logging { getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } - val channelId = + /// This must be lazy because we use Context + private val channelId: String by lazy() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { createNotificationChannel() } else { @@ -286,10 +286,11 @@ class MeshService : Service(), Logging { // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) "" } + } - private val mainAppIntent = Intent(this, MainActivity::class.java) - private val openAppIntent = PendingIntent.getActivity(this, 0, mainAppIntent, 0) - private val notificationBuilder = NotificationCompat.Builder(this, channelId) + private val openAppIntent: PendingIntent by lazy() { + PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java), 0) + } /// A text message that has a arrived since the last notification update private var recentReceivedText: TextMessage? = null @@ -298,7 +299,7 @@ class MeshService : Service(), Logging { get() = if (!isConnected) "No radio connected" else - "Connected $numOnlineNodes / $numNodes online" + "Connected: $numOnlineNodes of $numNodes online" override fun toString() = summaryString @@ -307,11 +308,13 @@ class MeshService : Service(), Logging { */ private fun createNotification(): Notification { + val notificationBuilder = NotificationCompat.Builder(this, channelId) + val builder = notificationBuilder.setOngoing(true) .setPriority(PRIORITY_MIN) .setCategory(if (recentReceivedText != null) Notification.CATEGORY_SERVICE else Notification.CATEGORY_MESSAGE) .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) - .setContentTitle("Meshtastic: $summaryString") // leave this off for now so our notification looks smaller + .setContentTitle(summaryString) // leave this off for now so our notification looks smaller .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setContentIntent(openAppIntent) @@ -643,7 +646,7 @@ class MeshService : Service(), Logging { /// If we just changed our nodedb, we might want to do somethings private fun onNodeDBChanged() { updateNotification() - + // we don't ask for GPS locations from android if our device has a built in GPS if (!myNodeInfo!!.hasGPS) { // If we have at least one other person in the mesh, send our GPS position otherwise stop listening to GPS diff --git a/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt index c4d20e7f..be485ac7 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt @@ -7,8 +7,10 @@ import android.os.ParcelUuid import androidx.compose.* import androidx.compose.frames.modelMapOf import androidx.ui.core.ContextAmbient +import androidx.ui.core.LayoutModifier import androidx.ui.core.Text import androidx.ui.layout.Column +import androidx.ui.layout.LayoutGravity import androidx.ui.material.CircularProgressIndicator import androidx.ui.material.EmphasisLevels import androidx.ui.material.ProvideEmphasis @@ -136,7 +138,7 @@ fun BTScanScreen() { Text("An unexpected error was encountered. Please file a bug on our github: ${ScanUIState.errorText}") } else { if (ScanUIState.devices.isEmpty()) { - Text("Looking for Meshtastic devices... (zero found)") + Text(text = "Looking for Meshtastic devices... (zero found)", modifier = LayoutGravity.Center) CircularProgressIndicator() // Show that we are searching still } else { diff --git a/app/src/main/res/drawable/ic_baseline_settings_input_antenna_24.xml b/app/src/main/res/drawable/ic_baseline_settings_input_antenna_24.xml new file mode 100644 index 00000000..9379c0f4 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_settings_input_antenna_24.xml @@ -0,0 +1,10 @@ + + +