kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
Merge remote-tracking branch 'upstream/master' into settings
commit
26d7ff9578
|
@ -3,6 +3,7 @@
|
|||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/app/src/main/proto" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/design" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/geeksville-androidlib" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/mesh_shared/src/main/proto" vcs="Git" />
|
||||
</component>
|
||||
|
|
|
@ -31,8 +31,8 @@ android {
|
|||
applicationId "com.geeksville.mesh"
|
||||
minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works)
|
||||
targetSdkVersion 29
|
||||
versionCode 20139 // format is Mmmss (where M is 1+the numeric major number
|
||||
versionName "1.1.39"
|
||||
versionCode 20142 // format is Mmmss (where M is 1+the numeric major number
|
||||
versionName "1.1.42"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
// per https://developer.android.com/studio/write/vector-asset-studio
|
||||
|
|
|
@ -73,7 +73,9 @@ class MeshService : Service(), Logging {
|
|||
|
||||
class IdNotFoundException(id: String) : Exception("ID not found $id")
|
||||
class NodeNumNotFoundException(id: Int) : Exception("NodeNum not found $id")
|
||||
class IsUpdatingException() : Exception("Operation prohibited during firmware update")
|
||||
|
||||
/** We treat software update as similar to loss of comms to the regular bluetooth service (so things like sendPosition for background GPS ignores the problem */
|
||||
class IsUpdatingException() : RadioNotConnectedException("Operation prohibited during firmware update")
|
||||
|
||||
/**
|
||||
* Talk to our running service and try to set a new device address. And then immediately
|
||||
|
@ -342,6 +344,9 @@ class MeshService : Service(), Logging {
|
|||
radio.close()
|
||||
saveSettings()
|
||||
|
||||
stopForeground(true) // Make sure we aren't using the notification first
|
||||
serviceNotifications.close()
|
||||
|
||||
super.onDestroy()
|
||||
serviceJob.cancel()
|
||||
}
|
||||
|
@ -1085,6 +1090,9 @@ class MeshService : Service(), Logging {
|
|||
|
||||
setFirmwareUpdateFilename(myInfo)
|
||||
|
||||
val a = RadioInterfaceService.getBondedDeviceAddress(this)
|
||||
val isBluetoothInterface = a != null && a.startsWith("x")
|
||||
|
||||
val mi = with(myInfo) {
|
||||
MyNodeInfo(
|
||||
myNodeNum,
|
||||
|
@ -1093,7 +1101,7 @@ class MeshService : Service(), Logging {
|
|||
hwModel,
|
||||
firmwareVersion,
|
||||
firmwareUpdateFilename != null,
|
||||
SoftwareUpdateService.shouldUpdate(
|
||||
isBluetoothInterface && SoftwareUpdateService.shouldUpdate(
|
||||
this@MeshService,
|
||||
DeviceVersion(firmwareVersion)
|
||||
),
|
||||
|
|
|
@ -6,27 +6,40 @@ import android.app.NotificationManager
|
|||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MainActivity
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.android.notificationManager
|
||||
import com.geeksville.mesh.utf8
|
||||
import java.io.Closeable
|
||||
|
||||
|
||||
class MeshServiceNotifications(
|
||||
private val context: Context
|
||||
) {
|
||||
) : Closeable
|
||||
{
|
||||
private val notificationManager: NotificationManager get() = context.notificationManager
|
||||
val notifyId = 101
|
||||
private var largeIcon: Bitmap? = null
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun createNotificationChannel(): String {
|
||||
val channelId = "my_service"
|
||||
val channelName = context.getString(R.string.meshtastic_service_notifications)
|
||||
val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH).apply {
|
||||
val channel = NotificationChannel(
|
||||
channelId,
|
||||
channelName,
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).apply {
|
||||
lightColor = Color.BLUE
|
||||
importance = NotificationManager.IMPORTANCE_NONE
|
||||
lockscreenVisibility = Notification.VISIBILITY_PRIVATE
|
||||
|
@ -61,6 +74,25 @@ class MeshServiceNotifications(
|
|||
PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a bitmap from a vector drawable (even on old builds)
|
||||
* https://stackoverflow.com/questions/33696488/getting-bitmap-from-vector-drawable
|
||||
*/
|
||||
fun getBitmapFromVectorDrawable(drawableId: Int): Bitmap {
|
||||
var drawable = ContextCompat.getDrawable(context, drawableId)!!
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
drawable = DrawableCompat.wrap(drawable).mutate()
|
||||
}
|
||||
val bitmap = Bitmap.createBitmap(
|
||||
drawable.intrinsicWidth,
|
||||
drawable.intrinsicHeight, Bitmap.Config.ARGB_8888
|
||||
)
|
||||
val canvas = Canvas(bitmap)
|
||||
drawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||
drawable.draw(canvas)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new version of our notification - reflecting current app state
|
||||
*/
|
||||
|
@ -69,11 +101,16 @@ class MeshServiceNotifications(
|
|||
summaryString: String,
|
||||
senderName: String
|
||||
): Notification {
|
||||
// We delay making this bitmap until we know we need it
|
||||
if(largeIcon == null)
|
||||
largeIcon = getBitmapFromVectorDrawable(R.mipmap.ic_launcher2)
|
||||
|
||||
val category = if (recentReceivedText != null) Notification.CATEGORY_SERVICE else Notification.CATEGORY_MESSAGE
|
||||
val builder = NotificationCompat.Builder(context, channelId).setOngoing(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||
.setCategory(category)
|
||||
.setSmallIcon(R.drawable.app_icon)
|
||||
.setSmallIcon(if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) R.drawable.app_icon_novect else R.drawable.app_icon) // vector form icons don't work reliably on older androids
|
||||
.setLargeIcon(largeIcon) // we must include a large icon because of a bug in cyanogenmod https://github.com/open-keychain/open-keychain/issues/1356#issue-89493995
|
||||
.setContentTitle(summaryString) // leave this off for now so our notification looks smaller
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentIntent(openAppIntent)
|
||||
|
@ -93,4 +130,9 @@ class MeshServiceNotifications(
|
|||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
largeIcon?.recycle()
|
||||
largeIcon = null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import kotlinx.coroutines.Job
|
|||
import kotlinx.coroutines.cancel
|
||||
|
||||
|
||||
class RadioNotConnectedException(message: String = "Not connected to radio") :
|
||||
open class RadioNotConnectedException(message: String = "Not connected to radio") :
|
||||
BLEException(message)
|
||||
|
||||
|
||||
|
|
|
@ -136,7 +136,7 @@ class MapFragment : ScreenFragment("Map"), Logging {
|
|||
* Mapbox native code can crash painfully if you ever call a mapbox view function while the view is not actively being show
|
||||
*/
|
||||
private val isViewVisible: Boolean
|
||||
get() = view != null && isResumed
|
||||
get() = view != null && isResumed && (mapView?.isDestroyed != false)
|
||||
|
||||
override fun onViewCreated(viewIn: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(viewIn, savedInstanceState)
|
||||
|
@ -145,9 +145,9 @@ class MapFragment : ScreenFragment("Map"), Logging {
|
|||
if ((requireContext().applicationContext as GeeksvilleApplication).isAnalyticsAllowed) {
|
||||
val vIn = viewIn.findViewById<MapView>(R.id.mapView)
|
||||
mapView = vIn
|
||||
vIn.onCreate(savedInstanceState)
|
||||
|
||||
mapView?.let { v ->
|
||||
v.onCreate(savedInstanceState)
|
||||
|
||||
// Each time the pane is shown start fetching new map info (we do this here instead of
|
||||
// onCreate because getMapAsync can die in native code if the view goes away)
|
||||
v.getMapAsync { map ->
|
||||
|
@ -205,14 +205,25 @@ class MapFragment : ScreenFragment("Map"), Logging {
|
|||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
mapView?.onDestroy()
|
||||
super.onDestroyView()
|
||||
mapView?.onDestroy()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
mapView?.onSaveInstanceState(outState)
|
||||
mapView?.let {
|
||||
if (!it.isDestroyed)
|
||||
it.onSaveInstanceState(outState)
|
||||
}
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun onLowMemory() {
|
||||
mapView?.let {
|
||||
if (!it.isDestroyed)
|
||||
it.onLowMemory()
|
||||
}
|
||||
super.onLowMemory()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -698,6 +698,56 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
binding.scanProgressBar.visibility = visible
|
||||
binding.deviceRadioGroup.visibility = visible
|
||||
}
|
||||
private fun updateDevicesButtons( devices: MutableMap<String, BTScanModel.DeviceListEntry>?) {
|
||||
// Remove the old radio buttons and repopulate
|
||||
binding.deviceRadioGroup.removeAllViews()
|
||||
|
||||
if(devices == null) return
|
||||
|
||||
val adapter = scanModel.bluetoothAdapter
|
||||
var hasShownOurDevice = false
|
||||
devices.values.forEach { device ->
|
||||
if (device.address == scanModel.selectedNotNull)
|
||||
hasShownOurDevice = true
|
||||
addDeviceButton(device, true)
|
||||
}
|
||||
|
||||
// The selected device is not in the scan; it is either offline, or it doesn't advertise
|
||||
// itself (most BLE devices don't advertise when connected).
|
||||
// Show it in the list, greyed out based on connection status.
|
||||
if (!hasShownOurDevice) {
|
||||
// Note: we pull this into a tempvar, because otherwise some other thread can change selectedAddress after our null check
|
||||
// and before use
|
||||
val bleAddr = scanModel.selectedBluetooth
|
||||
|
||||
if (bleAddr != null && adapter != null && adapter.isEnabled) {
|
||||
val bDevice =
|
||||
adapter.getRemoteDevice(bleAddr)
|
||||
if (bDevice.name != null) { // ignore nodes that node have a name, that means we've lost them since they appeared
|
||||
val curDevice = BTScanModel.DeviceListEntry(
|
||||
bDevice.name,
|
||||
scanModel.selectedAddress!!,
|
||||
bDevice.bondState == BOND_BONDED
|
||||
)
|
||||
addDeviceButton(curDevice, model.isConnected.value == MeshService.ConnectionState.CONNECTED)
|
||||
}
|
||||
} else if (scanModel.selectedUSB != null) {
|
||||
// Must be a USB device, show a placeholder disabled entry
|
||||
val curDevice = BTScanModel.DeviceListEntry(
|
||||
scanModel.selectedUSB!!,
|
||||
scanModel.selectedAddress!!,
|
||||
false
|
||||
)
|
||||
addDeviceButton(curDevice, false)
|
||||
}
|
||||
}
|
||||
|
||||
val hasBonded =
|
||||
RadioInterfaceService.getBondedDeviceAddress(requireContext()) != null
|
||||
|
||||
// get rid of the warning text once at least one device is paired
|
||||
binding.warningNotPaired.visibility = if (hasBonded) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
/// Setup the GUI to do a classic (pre SDK 26 BLE scan)
|
||||
private fun initClassicScan() {
|
||||
|
@ -719,54 +769,13 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
}
|
||||
})
|
||||
|
||||
scanModel.devices.observe(viewLifecycleOwner, Observer { devices ->
|
||||
// Remove the old radio buttons and repopulate
|
||||
binding.deviceRadioGroup.removeAllViews()
|
||||
scanModel.devices.observe(
|
||||
viewLifecycleOwner,
|
||||
Observer { devices -> updateDevicesButtons(devices) })
|
||||
|
||||
val adapter = scanModel.bluetoothAdapter
|
||||
|
||||
var hasShownOurDevice = false
|
||||
devices.values.forEach { device ->
|
||||
if (device.address == scanModel.selectedNotNull)
|
||||
hasShownOurDevice = true
|
||||
addDeviceButton(device, true)
|
||||
}
|
||||
|
||||
// The device the user is already paired with is offline currently, still show it
|
||||
// it in the list, but greyed out
|
||||
if (!hasShownOurDevice) {
|
||||
// Note: we pull this into a tempvar, because otherwise some other thread can change selectedAddress after our null check
|
||||
// and before use
|
||||
val bleAddr = scanModel.selectedBluetooth
|
||||
|
||||
if (bleAddr != null && adapter != null && adapter.isEnabled) {
|
||||
val bDevice =
|
||||
adapter.getRemoteDevice(bleAddr)
|
||||
if (bDevice.name != null) { // ignore nodes that node have a name, that means we've lost them since they appeared
|
||||
val curDevice = BTScanModel.DeviceListEntry(
|
||||
bDevice.name,
|
||||
scanModel.selectedAddress!!,
|
||||
bDevice.bondState == BOND_BONDED
|
||||
)
|
||||
addDeviceButton(curDevice, false)
|
||||
}
|
||||
} else if (scanModel.selectedUSB != null) {
|
||||
// Must be a USB device, show a placeholder disabled entry
|
||||
val curDevice = BTScanModel.DeviceListEntry(
|
||||
scanModel.selectedUSB!!,
|
||||
scanModel.selectedAddress!!,
|
||||
false
|
||||
)
|
||||
addDeviceButton(curDevice, false)
|
||||
}
|
||||
}
|
||||
|
||||
val hasBonded =
|
||||
RadioInterfaceService.getBondedDeviceAddress(requireContext()) != null
|
||||
|
||||
// get rid of the warning text once at least one device is paired
|
||||
binding.warningNotPaired.visibility = if (hasBonded) View.GONE else View.VISIBLE
|
||||
})
|
||||
model.isConnected.observe(
|
||||
viewLifecycleOwner,
|
||||
{ updateDevicesButtons(scanModel.devices.value) })
|
||||
}
|
||||
|
||||
/// Start running the modern scan, once it has one result we enable the
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
app_icon.png
|
|
@ -0,0 +1 @@
|
|||
app_icon.png
|
|
@ -0,0 +1 @@
|
|||
app_icon.png
|
|
@ -0,0 +1 @@
|
|||
app_icon.png
|
|
@ -1,7 +1,7 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.4.21'
|
||||
ext.kotlin_version = '1.4.30'
|
||||
ext.coroutines_version = "1.3.9"
|
||||
|
||||
repositories {
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit f3812d8484c571f62c72d1509a1e02357fda5b8e
|
||||
Subproject commit d7c3fa8ab6a47169e5dc8761d03d24588c3dd845
|
Ładowanie…
Reference in New Issue