kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
commit
bd541298cd
|
@ -176,6 +176,11 @@ dependencies {
|
|||
|
||||
// Add the Firebase SDK for Crashlytics.
|
||||
implementation 'com.google.firebase:firebase-crashlytics:18.2.6'
|
||||
implementation 'com.google.firebase:firebase-analytics:20.1.0'
|
||||
|
||||
// geeksville-androidlib
|
||||
compileOnly 'com.mixpanel.android:mixpanel-android:5.8.7'
|
||||
// compileOnly 'com.google.android.gms:play-services-base:17.6.0'
|
||||
|
||||
// alas implementation bug deep in the bowels when I tried it for my SyncBluetoothDevice class
|
||||
// implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
|
||||
|
@ -197,8 +202,6 @@ dependencies {
|
|||
|
||||
implementation "androidx.core:core-splashscreen:1.0.0-beta02"
|
||||
|
||||
implementation project(':geeksville-androidlib')
|
||||
|
||||
// App intro
|
||||
implementation 'com.github.AppIntro:AppIntro:6.2.0'
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package com.geeksville.mesh
|
|||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import java.util.*
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.geeksville.mesh
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.content.*
|
||||
|
@ -28,11 +27,11 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.geeksville.android.BindFailedException
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.android.ServiceClient
|
||||
import com.geeksville.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.android.BindFailedException
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.android.ServiceClient
|
||||
import com.geeksville.mesh.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.android.getMissingPermissions
|
||||
import com.geeksville.mesh.databinding.ActivityMainBinding
|
||||
import com.geeksville.mesh.model.BTScanModel
|
||||
|
@ -44,8 +43,8 @@ import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
|||
import com.geeksville.mesh.repository.radio.SerialInterface
|
||||
import com.geeksville.mesh.service.*
|
||||
import com.geeksville.mesh.ui.*
|
||||
import com.geeksville.util.Exceptions
|
||||
import com.geeksville.util.exceptionReporter
|
||||
import com.geeksville.mesh.util.Exceptions
|
||||
import com.geeksville.mesh.util.exceptionReporter
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
@ -656,21 +655,21 @@ class MainActivity : BaseActivity(), Logging {
|
|||
- waiting on an unknown object
|
||||
at java.util.concurrent.locks.LockSupport.park (LockSupport.java:190)
|
||||
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await (AbstractQueuedSynchronizer.java:2067)
|
||||
at com.geeksville.android.ServiceClient.waitConnect (ServiceClient.java:46)
|
||||
at com.geeksville.android.ServiceClient.getService (ServiceClient.java:27)
|
||||
at com.geeksville.mesh.android.ServiceClient.waitConnect (ServiceClient.java:46)
|
||||
at com.geeksville.mesh.android.ServiceClient.getService (ServiceClient.java:27)
|
||||
at com.geeksville.mesh.service.MeshService$binder$1$setDeviceAddress$1.invoke (MeshService.java:1519)
|
||||
at com.geeksville.mesh.service.MeshService$binder$1$setDeviceAddress$1.invoke (MeshService.java:1514)
|
||||
at com.geeksville.util.ExceptionsKt.toRemoteExceptions (ExceptionsKt.java:56)
|
||||
at com.geeksville.mesh.util.ExceptionsKt.toRemoteExceptions (ExceptionsKt.java:56)
|
||||
at com.geeksville.mesh.service.MeshService$binder$1.setDeviceAddress (MeshService.java:1516)
|
||||
at com.geeksville.mesh.MainActivity$mesh$1$onConnected$1.invoke (MainActivity.java:743)
|
||||
at com.geeksville.mesh.MainActivity$mesh$1$onConnected$1.invoke (MainActivity.java:734)
|
||||
at com.geeksville.util.ExceptionsKt.exceptionReporter (ExceptionsKt.java:34)
|
||||
at com.geeksville.mesh.util.ExceptionsKt.exceptionReporter (ExceptionsKt.java:34)
|
||||
at com.geeksville.mesh.MainActivity$mesh$1.onConnected (MainActivity.java:738)
|
||||
at com.geeksville.mesh.MainActivity$mesh$1.onConnected (MainActivity.java:734)
|
||||
at com.geeksville.android.ServiceClient$connection$1$onServiceConnected$1.invoke (ServiceClient.java:89)
|
||||
at com.geeksville.android.ServiceClient$connection$1$onServiceConnected$1.invoke (ServiceClient.java:84)
|
||||
at com.geeksville.util.ExceptionsKt.exceptionReporter (ExceptionsKt.java:34)
|
||||
at com.geeksville.android.ServiceClient$connection$1.onServiceConnected (ServiceClient.java:85)
|
||||
at com.geeksville.mesh.android.ServiceClient$connection$1$onServiceConnected$1.invoke (ServiceClient.java:89)
|
||||
at com.geeksville.mesh.android.ServiceClient$connection$1$onServiceConnected$1.invoke (ServiceClient.java:84)
|
||||
at com.geeksville.mesh.util.ExceptionsKt.exceptionReporter (ExceptionsKt.java:34)
|
||||
at com.geeksville.mesh.android.ServiceClient$connection$1.onServiceConnected (ServiceClient.java:85)
|
||||
at android.app.LoadedApk$ServiceDispatcher.doConnected (LoadedApk.java:2067)
|
||||
at android.app.LoadedApk$ServiceDispatcher$RunConnection.run (LoadedApk.java:2099)
|
||||
at android.os.Handler.handleCallback (Handler.java:883)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package com.geeksville.mesh
|
||||
|
||||
import android.os.Debug
|
||||
import com.geeksville.android.AppPrefs
|
||||
import com.geeksville.android.BuildUtils.isEmulator
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.util.Exceptions
|
||||
import com.geeksville.mesh.android.AppPrefs
|
||||
import com.geeksville.mesh.android.BuildUtils.isEmulator
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.util.Exceptions
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package com.geeksville.mesh
|
|||
import android.os.Parcelable
|
||||
import com.geeksville.mesh.util.bearing
|
||||
import com.geeksville.mesh.util.latLongToMeter
|
||||
import com.geeksville.util.anonymize
|
||||
import com.geeksville.mesh.util.anonymize
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
package com.geeksville.mesh.analytics
|
||||
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
|
||||
/**
|
||||
* Created by kevinh on 12/24/14.
|
||||
*/
|
||||
class DataPair(val name: String, valueIn: Any?) {
|
||||
val value = valueIn ?: "null"
|
||||
|
||||
/// An accumulating firebase event - only one allowed per event
|
||||
constructor(d: Double) : this(FirebaseAnalytics.Param.VALUE, d)
|
||||
constructor(d: Int) : this(FirebaseAnalytics.Param.VALUE, d)
|
||||
}
|
||||
|
||||
public interface AnalyticsProvider {
|
||||
|
||||
// Turn analytics logging on/off
|
||||
fun setEnabled(on: Boolean)
|
||||
|
||||
/**
|
||||
* Store an event
|
||||
*/
|
||||
fun track(event: String, vararg properties: DataPair): Unit
|
||||
|
||||
/**
|
||||
* Only track this event if using a cheap provider (like google)
|
||||
*/
|
||||
fun trackLowValue(event: String, vararg properties: DataPair): Unit
|
||||
|
||||
fun endSession(): Unit
|
||||
fun startSession(): Unit
|
||||
|
||||
/**
|
||||
* Set persistent ID info about this user, as a key value pair
|
||||
*/
|
||||
fun setUserInfo(vararg p: DataPair)
|
||||
|
||||
/**
|
||||
* Increment some sort of anyalytics counter
|
||||
*/
|
||||
fun increment(name: String, amount: Double = 1.0)
|
||||
|
||||
fun sendScreenView(name: String)
|
||||
fun endScreenView()
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package com.geeksville.mesh.analytics
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import com.geeksville.mesh.android.AppPrefs
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.Logging
|
||||
|
||||
/**
|
||||
* Implement our analytics API using firebase analtics
|
||||
*/
|
||||
class GoogleAnalytics(context: Context) : AnalyticsProvider, Logging {
|
||||
|
||||
val t = com.google.firebase.analytics.FirebaseAnalytics.getInstance(context)
|
||||
|
||||
init {
|
||||
val pref = AppPrefs(context)
|
||||
t.setUserId(pref.getInstallId())
|
||||
}
|
||||
|
||||
override fun setEnabled(on: Boolean) {
|
||||
t.setAnalyticsCollectionEnabled(on)
|
||||
}
|
||||
|
||||
override fun endSession() {
|
||||
track("End Session")
|
||||
// Mint.flush() // Send results now
|
||||
}
|
||||
|
||||
override fun trackLowValue(event: String, vararg properties: DataPair) {
|
||||
track(event, *properties)
|
||||
}
|
||||
|
||||
override fun track(event: String, vararg properties: DataPair) {
|
||||
debug("Analytics: track $event")
|
||||
|
||||
val bundle = Bundle()
|
||||
properties.forEach {
|
||||
when (it.value) {
|
||||
is Double -> bundle.putDouble(it.name, it.value)
|
||||
is Int -> bundle.putLong(it.name, it.value.toLong())
|
||||
is Long -> bundle.putLong(it.name, it.value)
|
||||
is Float -> bundle.putDouble(it.name, it.value.toDouble())
|
||||
else -> bundle.putString(it.name, it.value.toString())
|
||||
}
|
||||
}
|
||||
t.logEvent(event, bundle)
|
||||
}
|
||||
|
||||
override fun startSession() {
|
||||
debug("Analytics: start session")
|
||||
// automatic with firebase
|
||||
}
|
||||
|
||||
override fun setUserInfo(vararg p: DataPair) {
|
||||
p.forEach { t.setUserProperty(it.name, it.value.toString()) }
|
||||
}
|
||||
|
||||
override fun increment(name: String, amount: Double) {
|
||||
//Mint.logEvent("$name increment")
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a google analyics screen view event
|
||||
*/
|
||||
override fun sendScreenView(name: String) {
|
||||
debug("Analytics: start screen $name")
|
||||
GeeksvilleApplication.currentActivity?.let {
|
||||
t.setCurrentScreen(
|
||||
it, name, null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun endScreenView() {
|
||||
// debug("Analytics: end screen")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package com.geeksville.mesh.analytics
|
||||
|
||||
|
||||
import android.content.Context
|
||||
import com.geeksville.mesh.android.AppPrefs
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.mixpanel.android.mpmetrics.MixpanelAPI
|
||||
import org.json.JSONObject
|
||||
|
||||
|
||||
class MixpanelAnalytics(context: Context, apiToken: String, pushToken: String? = null) :
|
||||
AnalyticsProvider, Logging {
|
||||
// Initialize the library with your
|
||||
// Mixpanel project token, MIXPANEL_TOKEN, and a reference
|
||||
// to your application context.
|
||||
// See mixpanel docs at https://mixpanel.com/help/reference/android
|
||||
val mixpanel: MixpanelAPI = MixpanelAPI.getInstance(context, apiToken)
|
||||
val people = mixpanel.getPeople()!!
|
||||
|
||||
init {
|
||||
// fixupMixpanel()
|
||||
|
||||
// Assign a unique ID
|
||||
val pref = AppPrefs(context)
|
||||
val id = pref.getInstallId()
|
||||
debug("Connecting to mixpanel $id")
|
||||
mixpanel.identify(id)
|
||||
people.identify(id)
|
||||
}
|
||||
|
||||
private fun makeJSON(properties: Array<out DataPair>) =
|
||||
if (properties.isEmpty())
|
||||
null
|
||||
else {
|
||||
val r = JSONObject()
|
||||
properties.forEach { r.put(it.name, it.value) }
|
||||
r
|
||||
}
|
||||
|
||||
override fun trackLowValue(event: String, vararg properties: DataPair) {
|
||||
}
|
||||
|
||||
override fun setEnabled(on: Boolean) {
|
||||
if (on) mixpanel.optInTracking() else mixpanel.optOutTracking()
|
||||
}
|
||||
|
||||
override fun track(event: String, vararg properties: DataPair) {
|
||||
|
||||
debug("Tracking $event")
|
||||
val obj = makeJSON(properties)
|
||||
|
||||
mixpanel.track(event, obj)
|
||||
}
|
||||
|
||||
override fun endSession() {
|
||||
// track("End Session")
|
||||
mixpanel.flush()
|
||||
}
|
||||
|
||||
override fun startSession() {
|
||||
track("Start Session")
|
||||
}
|
||||
|
||||
override fun setUserInfo(vararg p: DataPair) {
|
||||
mixpanel.registerSuperProperties(makeJSON(p))
|
||||
}
|
||||
|
||||
override fun increment(name: String, amount: Double) {
|
||||
mixpanel.people.increment(name, amount)
|
||||
}
|
||||
|
||||
override fun sendScreenView(name: String) {
|
||||
// too verbose for mixpanel
|
||||
track(name)
|
||||
}
|
||||
|
||||
override fun endScreenView() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.geeksville.mesh.analytics
|
||||
|
||||
|
||||
/**
|
||||
* Created by kevinh on 12/24/14.
|
||||
*/
|
||||
|
||||
// Mint.initAndStartSession(MyActivity.this, "01a9c628");
|
||||
|
||||
/* disable for now because something in gradle doesn't like their lib repo
|
||||
import com.splunk.mint.Mint
|
||||
|
||||
class SplunkAnalytics(val context: Context, apiToken: String) : AnalyticsProvider, Logging {
|
||||
|
||||
private var inited = false
|
||||
|
||||
init {
|
||||
try {
|
||||
Mint.initAndStartSession(context, apiToken)
|
||||
inited = true
|
||||
} catch(ex: Exception) {
|
||||
error("exception logging failed to init")
|
||||
}
|
||||
}
|
||||
|
||||
override fun endSession() {
|
||||
track("End Session")
|
||||
if (inited)
|
||||
Mint.closeSession(context)
|
||||
// Mint.flush() // Send results now
|
||||
}
|
||||
|
||||
override fun trackLowValue(event: String, vararg properties: DataPair) {
|
||||
}
|
||||
|
||||
override fun track(event: String, vararg properties: DataPair) {
|
||||
if (inited)
|
||||
Mint.logEvent(event)
|
||||
}
|
||||
|
||||
override fun startSession() {
|
||||
if (inited) {
|
||||
Mint.startSession(context)
|
||||
track("Start Session")
|
||||
}
|
||||
}
|
||||
|
||||
override fun setUserInfo(vararg p: DataPair) {
|
||||
if (inited)
|
||||
p.forEach { Mint.addExtraData(it.name, it.value.toString()) }
|
||||
}
|
||||
|
||||
override fun increment(name: String, amount: Double) {
|
||||
if (inited)
|
||||
Mint.logEvent("$name increment")
|
||||
}
|
||||
|
||||
override fun sendScreenView(name: String) {
|
||||
}
|
||||
|
||||
override fun endScreenView() {
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
|
@ -0,0 +1,45 @@
|
|||
package com.geeksville.mesh.analytics
|
||||
|
||||
/**
|
||||
* Created by kevinh on 1/12/16.
|
||||
*/
|
||||
class TeeAnalytics(vararg providersIn: AnalyticsProvider) : AnalyticsProvider {
|
||||
|
||||
val providers = providersIn
|
||||
|
||||
override fun track(event: String, vararg properties: DataPair) {
|
||||
providers.forEach { it.track(event, *properties) }
|
||||
}
|
||||
|
||||
override fun trackLowValue(event: String, vararg properties: DataPair) {
|
||||
providers.forEach { it.trackLowValue(event, *properties) }
|
||||
}
|
||||
|
||||
override fun endSession() {
|
||||
providers.forEach { it.endSession() }
|
||||
}
|
||||
|
||||
override fun increment(name: String, amount: Double) {
|
||||
providers.forEach { it.increment(name, amount) }
|
||||
}
|
||||
|
||||
override fun setUserInfo(vararg p: DataPair) {
|
||||
providers.forEach { it.setUserInfo(*p) }
|
||||
}
|
||||
|
||||
override fun startSession() {
|
||||
providers.forEach { it.startSession() }
|
||||
}
|
||||
|
||||
override fun sendScreenView(name: String) {
|
||||
providers.forEach { it.sendScreenView(name) }
|
||||
}
|
||||
|
||||
override fun endScreenView() {
|
||||
providers.forEach { it.endScreenView() }
|
||||
}
|
||||
|
||||
override fun setEnabled(on: Boolean) {
|
||||
providers.forEach { it.setEnabled(on) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package com.geeksville.mesh.android
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import java.util.UUID
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* Created by kevinh on 1/4/15.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* A delegate for "foo by FloatPref"
|
||||
*/
|
||||
class FloatPref {
|
||||
fun get(thisRef: AppPrefs, prop: KProperty<Float>): Float = thisRef.getPrefs().getFloat(thisRef.makeName(prop.name), java.lang.Float.MIN_VALUE)
|
||||
|
||||
fun set(thisRef: AppPrefs, prop: KProperty<Float>, value: Float) {
|
||||
thisRef.setPrefs { e -> e.putFloat(thisRef.makeName(prop.name), value)}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A delegate for "foo by StringPref"
|
||||
*/
|
||||
class StringPref(val default: String) {
|
||||
fun get(thisRef: AppPrefs, prop: KProperty<String>): String = thisRef.getPrefs().getString(thisRef.makeName(prop.name), default)!!
|
||||
|
||||
fun set(thisRef: AppPrefs, prop: KProperty<String>, value: String) {
|
||||
thisRef.setPrefs { e ->
|
||||
e.putString(thisRef.makeName(prop.name), value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A mixin for accessing android prefs for the app
|
||||
*/
|
||||
public open class AppPrefs(val context: Context) {
|
||||
|
||||
companion object {
|
||||
private val baseName = "appPrefs_"
|
||||
}
|
||||
|
||||
fun makeName(s: String) = baseName + s
|
||||
|
||||
fun getPrefs() = context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
|
||||
|
||||
fun setPrefs(body: (SharedPreferences.Editor) -> Unit) {
|
||||
val e = getPrefs().edit()
|
||||
body(e)
|
||||
e.commit()
|
||||
}
|
||||
|
||||
fun incPref(name: String) {
|
||||
setPrefs { e ->
|
||||
e.putInt(name, 1 + getPrefs().getInt(name, 0))
|
||||
}
|
||||
}
|
||||
|
||||
fun removePref(name: String) {
|
||||
setPrefs { e ->
|
||||
e.remove(name)
|
||||
}
|
||||
}
|
||||
|
||||
fun putPref(name: String, b: Boolean) {
|
||||
setPrefs { e ->
|
||||
e.putBoolean(name, b)
|
||||
}
|
||||
}
|
||||
|
||||
fun putPref(name: String, b: Float) {
|
||||
setPrefs { e ->
|
||||
e.putFloat(name, b)
|
||||
}
|
||||
}
|
||||
|
||||
fun putPref(name: String, b: Int) {
|
||||
setPrefs { e ->
|
||||
e.putInt(name, b)
|
||||
}
|
||||
}
|
||||
|
||||
fun putPref(name: String, b: Set<String>) {
|
||||
setPrefs { e ->
|
||||
e.putStringSet(name, b)
|
||||
}
|
||||
}
|
||||
|
||||
fun putPref(name: String, b: String) {
|
||||
setPrefs { e ->
|
||||
e.putString(name, b)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a persistent installation ID
|
||||
*/
|
||||
fun getInstallId(): String {
|
||||
var r = getPrefs().getString(makeName("installId"), "")!!
|
||||
if(r == "") {
|
||||
r = UUID.randomUUID().toString()
|
||||
putPref(makeName("installId"), r)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package com.geeksville.mesh.android
|
||||
|
||||
import android.os.Build
|
||||
|
||||
/**
|
||||
* Created by kevinh on 1/14/16.
|
||||
*/
|
||||
object BuildUtils : Logging {
|
||||
|
||||
fun is64Bit(): Boolean {
|
||||
if (Build.VERSION.SDK_INT < 21)
|
||||
return false
|
||||
else
|
||||
return Build.SUPPORTED_64_BIT_ABIS.size > 0
|
||||
}
|
||||
|
||||
fun isBuggyMoto(): Boolean {
|
||||
debug("Device type is: ${Build.DEVICE}")
|
||||
return Build.DEVICE == "osprey_u2" // Moto G
|
||||
}
|
||||
|
||||
/// Are we running on the emulator?
|
||||
val isEmulator get() = Build.MODEL == "Android SDK built for x86" || Build.MODEL == "sdk_gphone_x86" || Build.MODEL == "Android SDK built for x86_64"
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.geeksville.mesh.android
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
|
||||
/// show a toast
|
||||
fun Context.toast(message: CharSequence) =
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||
|
||||
/// Utility function to hide the soft keyboard per stack overflow
|
||||
fun Activity.hideKeyboard() {
|
||||
// Check if no view has focus:
|
||||
currentFocus?.let { v ->
|
||||
val imm =
|
||||
getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
imm?.hideSoftInputFromWindow(v.windowToken, 0)
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ import android.content.pm.PackageManager
|
|||
import android.hardware.usb.UsbManager
|
||||
import android.os.Build
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.MainActivity
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package com.geeksville.mesh.android
|
||||
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by kevinh on 1/13/16.
|
||||
*/
|
||||
object DateUtils {
|
||||
fun dateUTC(year: Int, month: Int, day: Int): Date {
|
||||
val cal = GregorianCalendar(TimeZone.getTimeZone("GMT"))
|
||||
cal.set(year, month, day, 0, 0, 0);
|
||||
return Date(cal.getTime().getTime())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package com.geeksville.mesh.android
|
||||
|
||||
import android.content.Context
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.PrintWriter
|
||||
|
||||
/**
|
||||
* Create a debug log on the SD card (if needed and allowed and app is configured for debugging (FIXME)
|
||||
*
|
||||
* write strings to that file
|
||||
*/
|
||||
class DebugLogFile(context: Context, name: String) {
|
||||
val stream = FileOutputStream(File(context.getExternalFilesDir(null), name), true)
|
||||
val file = PrintWriter(stream)
|
||||
|
||||
fun close() {
|
||||
file.close()
|
||||
}
|
||||
|
||||
fun log(s: String) {
|
||||
file.println(s) // FIXME, optionally include timestamps
|
||||
file.flush() // for debugging
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a debug log on the SD card (if needed and allowed and app is configured for debugging (FIXME)
|
||||
*
|
||||
* write strings to that file
|
||||
*/
|
||||
class BinaryLogFile(context: Context, name: String) :
|
||||
FileOutputStream(File(context.getExternalFilesDir(null), name), true) {
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package com.geeksville.mesh.android
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by kevinh on 1/13/16.
|
||||
*/
|
||||
class ExpireChecker(val context: Activity) : Logging {
|
||||
|
||||
fun check(year: Int, month: Int, day: Int) {
|
||||
val expireDate = DateUtils.dateUTC(year, month, day)
|
||||
val now = Date()
|
||||
|
||||
debug("Expire check $now vs $expireDate")
|
||||
if (now.after(expireDate))
|
||||
doExpire()
|
||||
}
|
||||
|
||||
private fun doExpire() {
|
||||
val packageName = context.packageName
|
||||
errormsg("$packageName is too old and must be updated at the Play store")
|
||||
|
||||
Toast.makeText(
|
||||
context,
|
||||
"This application is out of date and must be updated",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
val i = Intent(Intent.ACTION_VIEW)
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
i.setData(Uri.parse("market://details?id=$packageName&referrer=utm_source%3Dexpired"))
|
||||
context.startActivity(i)
|
||||
context.finish()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package com.geeksville.mesh.android
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import androidx.core.content.edit
|
||||
import com.geeksville.mesh.analytics.AnalyticsProvider
|
||||
import com.geeksville.mesh.analytics.MixpanelAnalytics
|
||||
import com.geeksville.mesh.analytics.TeeAnalytics
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
|
||||
|
||||
fun isGooglePlayAvailable(context: Context): Boolean {
|
||||
val a = GoogleApiAvailability.getInstance()
|
||||
val r = a.isGooglePlayServicesAvailable(context)
|
||||
return r != ConnectionResult.SERVICE_MISSING && r != ConnectionResult.SERVICE_INVALID
|
||||
}
|
||||
|
||||
/**
|
||||
* Created by kevinh on 1/4/15.
|
||||
*/
|
||||
|
||||
open class GeeksvilleApplication(
|
||||
val splunkKey: String? = null,
|
||||
val mixpanelKey: String? = null,
|
||||
val pushKey: String? = null
|
||||
) : Application(), Logging {
|
||||
|
||||
companion object {
|
||||
lateinit var analytics: AnalyticsProvider
|
||||
var currentActivity: Activity? = null
|
||||
}
|
||||
|
||||
var splunk: AnalyticsProvider? = null
|
||||
var mixAnalytics: MixpanelAnalytics? = null
|
||||
|
||||
private val lifecycleCallbacks = object : ActivityLifecycleCallbacks {
|
||||
override fun onActivityPaused(activity: Activity) {
|
||||
}
|
||||
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
}
|
||||
|
||||
override fun onActivityDestroyed(activity: Activity) {
|
||||
currentActivity = null
|
||||
}
|
||||
|
||||
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
|
||||
}
|
||||
|
||||
override fun onActivityStopped(activity: Activity) {
|
||||
}
|
||||
|
||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||
currentActivity = activity
|
||||
}
|
||||
|
||||
override fun onActivityResumed(activity: Activity) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Are we running inside the testlab?
|
||||
val isInTestLab: Boolean
|
||||
get() {
|
||||
val testLabSetting =
|
||||
Settings.System.getString(contentResolver, "firebase.test.lab") ?: null
|
||||
if(testLabSetting != null)
|
||||
info("Testlab is $testLabSetting")
|
||||
return "true" == testLabSetting
|
||||
}
|
||||
|
||||
private val analyticsPrefs: SharedPreferences by lazy {
|
||||
getSharedPreferences(
|
||||
"analytics-prefs",
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
}
|
||||
|
||||
var isAnalyticsAllowed: Boolean
|
||||
get() = analyticsPrefs.getBoolean("allowed", true)
|
||||
set(value) {
|
||||
analyticsPrefs.edit(commit = true) {
|
||||
putBoolean("allowed", value)
|
||||
}
|
||||
|
||||
// Change the flag with the providers
|
||||
analytics.setEnabled(value && !isInTestLab) // Never do analytics in the test lab
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super<Application>.onCreate()
|
||||
|
||||
/*
|
||||
if(splunkKey != null)
|
||||
splunk = SplunkAnalytics(this, splunkKey) // Only used for crash reports
|
||||
*/
|
||||
|
||||
val googleAnalytics = com.geeksville.mesh.analytics.GoogleAnalytics(this)
|
||||
if (mixpanelKey != null) {
|
||||
val mix = com.geeksville.mesh.analytics.MixpanelAnalytics(this, mixpanelKey, pushKey)
|
||||
mixAnalytics = mix
|
||||
|
||||
analytics = TeeAnalytics(googleAnalytics, mix)
|
||||
} else
|
||||
analytics = googleAnalytics
|
||||
|
||||
// Set analytics per prefs
|
||||
isAnalyticsAllowed = isAnalyticsAllowed
|
||||
|
||||
registerActivityLifecycleCallbacks(lifecycleCallbacks)
|
||||
}
|
||||
|
||||
fun isInternetConnected(): Boolean {
|
||||
val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
val activeNetwork = cm.getActiveNetworkInfo();
|
||||
val isConnected = activeNetwork != null &&
|
||||
activeNetwork.isConnectedOrConnecting();
|
||||
|
||||
return isConnected
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun geeksvilleApp(context: Context) = context.applicationContext as GeeksvilleApplication
|
||||
|
||||
|
||||
interface GeeksvilleApplicationClient {
|
||||
|
||||
fun getAnalytics() = GeeksvilleApplication.analytics
|
||||
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package com.geeksville.mesh.android
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.geeksville.mesh.BuildConfig
|
||||
import com.geeksville.mesh.util.Exceptions
|
||||
|
||||
/**
|
||||
* Created by kevinh on 12/24/14.
|
||||
*/
|
||||
|
||||
typealias LogPrinter = (Int, String, String) -> Unit
|
||||
|
||||
interface Logging {
|
||||
|
||||
companion object {
|
||||
/** Some vendors strip log messages unless the severity is super high.
|
||||
*
|
||||
* alps == Soyes
|
||||
* HMD Global == mfg of the Nokia 7.2
|
||||
*/
|
||||
private val badVendors = setOf("OnePlus", "alps", "HMD Global", "Sony")
|
||||
|
||||
/// if false NO logs will be shown, set this in the application based on BuildConfig.DEBUG
|
||||
var showLogs = true
|
||||
|
||||
/** if true, all logs will be printed at error level. Sometimes necessary for buggy ROMs
|
||||
* that filter logcat output below this level.
|
||||
*
|
||||
* Since there are so many bad vendors, we just always lie if we are a release build
|
||||
*/
|
||||
var forceErrorLevel = !BuildConfig.DEBUG || badVendors.contains(Build.MANUFACTURER)
|
||||
|
||||
/// If false debug logs will not be shown (but others might)
|
||||
var showDebug = true
|
||||
|
||||
/**
|
||||
* By default all logs are printed using the standard android Log class. But clients
|
||||
* can change printlog to a different implementation (for logging to files or via
|
||||
* google crashlytics)
|
||||
*/
|
||||
var printlog: LogPrinter = { level, tag, message ->
|
||||
if (showLogs) {
|
||||
if (showDebug || level > Log.DEBUG) {
|
||||
Log.println(if (forceErrorLevel) Log.ERROR else level, tag, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun tag(): String = this.javaClass.getName()
|
||||
|
||||
fun info(msg: String) = printlog(Log.INFO, tag(), msg)
|
||||
fun verbose(msg: String) = printlog(Log.VERBOSE, tag(), msg)
|
||||
fun debug(msg: String) = printlog(Log.DEBUG, tag(), msg)
|
||||
fun warn(msg: String) = printlog(Log.WARN, tag(), msg)
|
||||
|
||||
/**
|
||||
* Log an error message, note - we call this errormsg rather than error because error() is
|
||||
* a stdlib function in kotlin in the global namespace and we don't want users to accidentally call that.
|
||||
*/
|
||||
fun errormsg(msg: String, ex: Throwable? = null) {
|
||||
if (ex?.message != null)
|
||||
printlog(Log.ERROR, tag(), "$msg (exception ${ex.message})")
|
||||
else
|
||||
printlog(Log.ERROR, tag(), "$msg")
|
||||
}
|
||||
|
||||
/// Kotlin assertions are disabled on android, so instead we use this assert helper
|
||||
fun logAssert(f: Boolean) {
|
||||
if (!f) {
|
||||
val ex = AssertionError("Assertion failed")
|
||||
|
||||
// if(!Debug.isDebuggerConnected())
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
||||
/// Report an error (including messaging our crash reporter service if allowed
|
||||
fun reportError(s: String) {
|
||||
Exceptions.report(Exception("logging reportError: $s"), s)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
package com.geeksville.mesh.android
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import com.google.android.gms.common.api.Api
|
||||
import com.google.android.gms.common.api.Api.ApiOptions.NotRequiredOptions
|
||||
import com.google.android.gms.common.api.Scope
|
||||
import com.google.android.gms.common.api.GoogleApiClient
|
||||
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GooglePlayServicesUtil
|
||||
import android.content.IntentSender
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
|
||||
|
||||
interface PlayClientCallbacks /* : Activity */ {
|
||||
/**
|
||||
* Called to tell activity we've lost connection to play
|
||||
*/
|
||||
fun onPlayConnectionSuspended() :Unit
|
||||
|
||||
/**
|
||||
* Called to tell activity we are now connected to play
|
||||
* Do remaining init here
|
||||
*/
|
||||
fun onPlayConnected() : Unit
|
||||
|
||||
/**
|
||||
* Called when this machine does not have a valid form of play.
|
||||
*/
|
||||
fun onPlayUnavailable() : Unit
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Created by kevinh on 1/5/15.
|
||||
*/
|
||||
|
||||
public class PlayClient(val context: Activity, val playCallbacks: PlayClientCallbacks) : Logging {
|
||||
|
||||
var apiClient: GoogleApiClient? = null
|
||||
var authInProgress: Boolean = false
|
||||
|
||||
companion object {
|
||||
val PLAY_OAUTH_REQUEST_CODE = 901
|
||||
val AUTH_PENDING = "authPend"
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Must be called from onCreate
|
||||
*/
|
||||
fun playOnCreate(savedInstanceState: Bundle?, apis: Array<Api<out NotRequiredOptions>>, scopes: Array<Scope> = arrayOf()) {
|
||||
|
||||
if(savedInstanceState != null)
|
||||
authInProgress = savedInstanceState.getBoolean(AUTH_PENDING)
|
||||
|
||||
if(hasPlayServices()) {
|
||||
var builder = GoogleApiClient.Builder(context)
|
||||
.addConnectionCallbacks(object : GoogleApiClient.ConnectionCallbacks {
|
||||
|
||||
override fun onConnected(p0: Bundle?) {
|
||||
// Connected to Google Play services!
|
||||
// The good stuff goes here.
|
||||
|
||||
playCallbacks.onPlayConnected()
|
||||
}
|
||||
|
||||
override fun onConnectionSuspended(i: Int) {
|
||||
// If your connection to the sensor gets lost at some point,
|
||||
// you'll be able to determine the reason and react to it here.
|
||||
if (i == ConnectionCallbacks.CAUSE_NETWORK_LOST) {
|
||||
info("Connection lost. Cause: Network Lost.");
|
||||
} else if (i == ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
|
||||
info("Connection lost. Reason: Service Disconnected");
|
||||
} else
|
||||
errormsg("Unknown play kode $i")
|
||||
|
||||
playCallbacks.onPlayConnectionSuspended()
|
||||
}
|
||||
})
|
||||
.addOnConnectionFailedListener(object : GoogleApiClient.OnConnectionFailedListener {
|
||||
override fun onConnectionFailed(result: ConnectionResult) {
|
||||
info("Play connection failed $result")
|
||||
if (!result.hasResolution()) {
|
||||
showErrorDialog(result.errorCode)
|
||||
} else {
|
||||
// The failure has a resolution. Resolve it.
|
||||
// Called typically when the app is not yet authorized, and an
|
||||
// authorization dialog is displayed to the user.
|
||||
if (!authInProgress) {
|
||||
try {
|
||||
info("Attempting to resolve failed connection");
|
||||
authInProgress = true;
|
||||
result.startResolutionForResult(context,
|
||||
PLAY_OAUTH_REQUEST_CODE);
|
||||
} catch (e: IntentSender.SendIntentException) {
|
||||
errormsg("Exception while starting resolution activity")
|
||||
playCallbacks.onPlayUnavailable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
apis.forEach { api ->
|
||||
builder = builder.addApi(api)
|
||||
}
|
||||
|
||||
scopes.forEach { s ->
|
||||
builder = builder.addScope(s)
|
||||
}
|
||||
|
||||
apiClient = builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showErrorDialog(code: Int) {
|
||||
// Show the localized error dialog
|
||||
GooglePlayServicesUtil.getErrorDialog(code,
|
||||
context, 0)?.show();
|
||||
playCallbacks.onPlayUnavailable()
|
||||
}
|
||||
|
||||
fun hasPlayServices(): Boolean {
|
||||
// Check that Google Play services is available
|
||||
val resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context)
|
||||
// For testing
|
||||
//val resultCode = ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED
|
||||
|
||||
if (ConnectionResult.SUCCESS == resultCode) {
|
||||
// In debug mode, log the status
|
||||
Log.d("Geofence Detection",
|
||||
"Google Play services is available.");
|
||||
|
||||
// getAnalytics().track("Has Play")
|
||||
|
||||
// Continue
|
||||
return true
|
||||
// Google Play services was not available for some reason
|
||||
} else {
|
||||
showErrorDialog(resultCode)
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called from onActivityResult
|
||||
* @return true if we handled this
|
||||
*/
|
||||
fun playOnActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean =
|
||||
if (requestCode == PLAY_OAUTH_REQUEST_CODE) {
|
||||
authInProgress = false;
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
// Make sure the app is not already connected or attempting to connect
|
||||
if (!apiClient!!.isConnecting && !apiClient!!.isConnected) {
|
||||
apiClient!!.connect();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// User opted to not install play
|
||||
errormsg("User declined play")
|
||||
context.finish()
|
||||
}
|
||||
true
|
||||
}
|
||||
else
|
||||
false
|
||||
|
||||
fun playOnStart() {
|
||||
if(apiClient != null)
|
||||
apiClient!!.connect()
|
||||
}
|
||||
|
||||
fun playOnStop() {
|
||||
if(apiClient != null && apiClient!!.isConnected)
|
||||
apiClient!!.disconnect()
|
||||
}
|
||||
|
||||
fun playSaveInstanceState(outState: Bundle) {
|
||||
outState.putBoolean(AUTH_PENDING, authInProgress)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package com.geeksville.mesh.android
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import android.os.IInterface
|
||||
import com.geeksville.mesh.util.exceptionReporter
|
||||
import java.io.Closeable
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
class BindFailedException : Exception("bindService failed")
|
||||
|
||||
/**
|
||||
* A wrapper that cleans up the service binding process
|
||||
*/
|
||||
open class ServiceClient<T : IInterface>(private val stubFactory: (IBinder) -> T) : Closeable,
|
||||
Logging {
|
||||
|
||||
var serviceP: T? = null
|
||||
|
||||
/// A getter that returns the bound service or throws if not bound
|
||||
val service: T
|
||||
get() {
|
||||
waitConnect() // Wait for at least the initial connection to happen
|
||||
return serviceP ?: throw Exception("Service not bound")
|
||||
}
|
||||
|
||||
private var context: Context? = null
|
||||
|
||||
private var isClosed = true
|
||||
|
||||
private val lock = ReentrantLock()
|
||||
private val condition = lock.newCondition()
|
||||
|
||||
/** Call this if you want to stall until the connection is completed */
|
||||
fun waitConnect() {
|
||||
// Wait until this service is connected
|
||||
lock.withLock {
|
||||
if (context == null)
|
||||
throw Exception("Haven't called connect")
|
||||
|
||||
if (serviceP == null)
|
||||
condition.await()
|
||||
}
|
||||
}
|
||||
|
||||
fun connect(c: Context, intent: Intent, flags: Int) {
|
||||
context = c
|
||||
if (isClosed) {
|
||||
isClosed = false
|
||||
if (!c.bindService(intent, connection, flags)) {
|
||||
|
||||
// Some phones seem to ahve a race where if you unbind and quickly rebind bindService returns false. Try
|
||||
// a short sleep to see if that helps
|
||||
errormsg("Needed to use the second bind attempt hack")
|
||||
Thread.sleep(500) // was 200ms, but received an autobug from a Galaxy Note4, android 6.0.1
|
||||
if (!c.bindService(intent, connection, flags)) {
|
||||
throw BindFailedException()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn("Ignoring rebind attempt for service")
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
isClosed = true
|
||||
try {
|
||||
context?.unbindService(connection)
|
||||
}
|
||||
catch(ex: IllegalArgumentException) {
|
||||
// Autobugs show this can generate an illegal arg exception for "service not registered" during reinstall?
|
||||
warn("Ignoring error in ServiceClient.close, probably harmless")
|
||||
}
|
||||
serviceP = null
|
||||
context = null
|
||||
}
|
||||
|
||||
/// Called when we become connected
|
||||
open fun onConnected(service: T) {
|
||||
}
|
||||
|
||||
/// called on loss of connection
|
||||
open fun onDisconnected() {
|
||||
}
|
||||
|
||||
private val connection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) = exceptionReporter {
|
||||
if (!isClosed) {
|
||||
val s = stubFactory(binder)
|
||||
serviceP = s
|
||||
onConnected(s)
|
||||
|
||||
// after calling our handler, tell anyone who was waiting for this connection to complete
|
||||
lock.withLock {
|
||||
condition.signalAll()
|
||||
}
|
||||
} else {
|
||||
// If we start to close a service, it seems that there is a possibility a onServiceConnected event is the queue
|
||||
// for us. Be careful not to process that stale event
|
||||
warn("A service connected while we were closing it, ignoring")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) = exceptionReporter {
|
||||
serviceP = null
|
||||
onDisconnected()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.geeksville.mesh.concurrent
|
||||
|
||||
import com.geeksville.mesh.util.Exceptions
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
private val errorHandler =
|
||||
CoroutineExceptionHandler { _, exception ->
|
||||
Exceptions.report(
|
||||
exception,
|
||||
"MeshService-coroutine",
|
||||
"coroutine-exception"
|
||||
)
|
||||
}
|
||||
|
||||
/// Wrap launch with an exception handler, FIXME, move into a utility lib
|
||||
fun CoroutineScope.handledLaunch(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
block: suspend CoroutineScope.() -> Unit
|
||||
) = this.launch(
|
||||
context = context + com.geeksville.mesh.concurrent.errorHandler,
|
||||
start = start,
|
||||
block = block
|
||||
)
|
|
@ -0,0 +1,30 @@
|
|||
package com.geeksville.mesh.concurrent
|
||||
|
||||
import com.geeksville.mesh.android.Logging
|
||||
|
||||
|
||||
/**
|
||||
* Sometimes when starting services we face situations where messages come in that require computation
|
||||
* but we can't do that computation yet because we are still waiting for some long running init to
|
||||
* complete.
|
||||
*
|
||||
* This class lets you queue up closures to run at a later date and later on you can call run() to
|
||||
* run all the previously queued work.
|
||||
*/
|
||||
class DeferredExecution : Logging {
|
||||
private val queue = mutableListOf<() -> Unit>()
|
||||
|
||||
/// Queue some new work
|
||||
fun add(fn: () -> Unit) {
|
||||
queue.add(fn)
|
||||
}
|
||||
|
||||
/// run all work in the queue and clear it to be ready to accept new work
|
||||
fun run() {
|
||||
debug("Running deferred execution numjobs=${queue.size}")
|
||||
queue.forEach {
|
||||
it()
|
||||
}
|
||||
queue.clear()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package com.geeksville.mesh.concurrent
|
||||
|
||||
import com.geeksville.mesh.android.Logging
|
||||
|
||||
|
||||
/**
|
||||
* A deferred execution object (with various possible implementations)
|
||||
*/
|
||||
interface Continuation<in T> : Logging {
|
||||
abstract fun resume(res: Result<T>)
|
||||
|
||||
// syntactic sugar
|
||||
|
||||
fun resumeSuccess(res: T) = resume(Result.success(res))
|
||||
fun resumeWithException(ex: Throwable) = try {
|
||||
resume(Result.failure(ex))
|
||||
} catch (ex: Throwable) {
|
||||
// errormsg("Ignoring $ex while resuming, because we are the ones who threw it")
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An async continuation that just calls a callback when the result is available
|
||||
*/
|
||||
class CallbackContinuation<in T>(private val cb: (Result<T>) -> Unit) : Continuation<T> {
|
||||
override fun resume(res: Result<T>) = cb(res)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a blocking/threaded version of coroutine Continuation
|
||||
*
|
||||
* A little bit ugly, but the coroutine version has a nasty internal bug that showed up
|
||||
* in my SyncBluetoothDevice so I needed a quick workaround.
|
||||
*/
|
||||
class SyncContinuation<T> : Continuation<T> {
|
||||
|
||||
private val mbox = java.lang.Object()
|
||||
private var result: Result<T>? = null
|
||||
|
||||
override fun resume(res: Result<T>) {
|
||||
synchronized(mbox) {
|
||||
result = res
|
||||
mbox.notify()
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the result (or throw an exception)
|
||||
fun await(timeoutMsecs: Long = 0): T {
|
||||
synchronized(mbox) {
|
||||
val startT = System.currentTimeMillis()
|
||||
while (result == null) {
|
||||
mbox.wait(timeoutMsecs)
|
||||
|
||||
if (timeoutMsecs > 0 && ((System.currentTimeMillis() - startT) >= timeoutMsecs))
|
||||
throw Exception("SyncContinuation timeout")
|
||||
}
|
||||
|
||||
val r = result
|
||||
if (r != null)
|
||||
return r.getOrThrow()
|
||||
else
|
||||
throw Exception("This shouldn't happen")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls an init function which is responsible for saving our continuation so that some
|
||||
* other thread can call resume or resume with exception.
|
||||
*
|
||||
* Essentially this is a blocking version of the (buggy) coroutine suspendCoroutine
|
||||
*/
|
||||
fun <T> suspend(timeoutMsecs: Long = -1, initfn: (SyncContinuation<T>) -> Unit): T {
|
||||
val cont = SyncContinuation<T>()
|
||||
|
||||
// First call the init funct
|
||||
initfn(cont)
|
||||
|
||||
// Now wait for the continuation to finish
|
||||
return cont.await(timeoutMsecs)
|
||||
}
|
|
@ -17,8 +17,8 @@ import androidx.activity.result.IntentSenderRequest
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.MainActivity
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.android.*
|
||||
|
@ -30,8 +30,8 @@ import com.geeksville.mesh.repository.radio.SerialInterface
|
|||
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||
import com.geeksville.mesh.ui.SLogging
|
||||
import com.geeksville.mesh.ui.changeDeviceSelection
|
||||
import com.geeksville.util.anonymize
|
||||
import com.geeksville.util.exceptionReporter
|
||||
import com.geeksville.mesh.util.anonymize
|
||||
import com.geeksville.mesh.util.exceptionReporter
|
||||
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
|
@ -3,7 +3,7 @@ package com.geeksville.mesh.model
|
|||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.AppOnlyProtos
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.MultiFormatWriter
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package com.geeksville.mesh.model
|
||||
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
|
||||
/**
|
||||
* Provide structured access to parse and compare device version strings
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.geeksville.mesh.model
|
|||
|
||||
import android.os.RemoteException
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.*
|
||||
import com.geeksville.mesh.database.PacketRepository
|
||||
import com.geeksville.mesh.database.QuickChatActionRepository
|
||||
|
|
|
@ -5,7 +5,7 @@ import android.content.BroadcastReceiver
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import com.geeksville.util.exceptionReporter
|
||||
import com.geeksville.mesh.util.exceptionReporter
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,7 +7,7 @@ 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.android.Logging
|
||||
import com.geeksville.mesh.CoroutineDispatchers
|
||||
import com.geeksville.mesh.android.hasBluetoothPermission
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.geeksville.mesh.repository.datastore
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
|
@ -4,8 +4,8 @@ import android.annotation.SuppressLint
|
|||
import android.content.Context
|
||||
import android.location.Location
|
||||
import android.os.Looper
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.android.hasBackgroundPermission
|
||||
import com.google.android.gms.location.LocationCallback
|
||||
import com.google.android.gms.location.LocationRequest
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.geeksville.mesh.repository.nsd
|
|||
|
||||
import android.net.nsd.NsdManager
|
||||
import android.net.nsd.NsdServiceInfo
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.CoroutineDispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
|
@ -6,15 +6,13 @@ import android.bluetooth.BluetoothGattCharacteristic
|
|||
import android.bluetooth.BluetoothGattService
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||
import com.geeksville.mesh.service.*
|
||||
import com.geeksville.util.anonymize
|
||||
import com.geeksville.util.exceptionReporter
|
||||
import com.geeksville.util.ignoreException
|
||||
import com.geeksville.mesh.util.anonymize
|
||||
import com.geeksville.mesh.util.exceptionReporter
|
||||
import com.geeksville.mesh.util.ignoreException
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package com.geeksville.mesh.repository.radio
|
||||
|
||||
import android.content.Context
|
||||
import com.geeksville.android.BuildUtils
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.BuildUtils
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.*
|
||||
import com.geeksville.mesh.model.getInitials
|
||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.geeksville.mesh.repository.radio
|
||||
|
||||
import android.content.Context
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||
|
||||
class NopInterface : IRadioInterface {
|
||||
|
|
|
@ -6,16 +6,16 @@ import android.content.SharedPreferences
|
|||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.Lifecycle
|
||||
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.android.BinaryLogFile
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.CoroutineDispatchers
|
||||
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
|
||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||
import com.geeksville.util.anonymize
|
||||
import com.geeksville.util.ignoreException
|
||||
import com.geeksville.util.toRemoteExceptions
|
||||
import com.geeksville.mesh.util.anonymize
|
||||
import com.geeksville.mesh.util.ignoreException
|
||||
import com.geeksville.mesh.util.toRemoteExceptions
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.geeksville.mesh.repository.radio
|
||||
|
||||
import android.content.Context
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.android.usbManager
|
||||
import com.geeksville.mesh.repository.usb.SerialConnection
|
||||
import com.geeksville.mesh.repository.usb.SerialConnectionListener
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package com.geeksville.mesh.repository.radio
|
||||
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package com.geeksville.mesh.repository.radio
|
||||
|
||||
import android.content.Context
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||
import com.geeksville.util.Exceptions
|
||||
import com.geeksville.mesh.util.Exceptions
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package com.geeksville.mesh.repository.usb
|
||||
|
||||
import android.hardware.usb.UsbManager
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.util.ignoreException
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.util.ignoreException
|
||||
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
||||
import com.hoho.android.usbserial.driver.UsbSerialPort
|
||||
import com.hoho.android.usbserial.util.SerialInputOutputManager
|
||||
|
|
|
@ -6,8 +6,8 @@ import android.content.Intent
|
|||
import android.content.IntentFilter
|
||||
import android.hardware.usb.UsbDevice
|
||||
import android.hardware.usb.UsbManager
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.util.exceptionReporter
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.util.exceptionReporter
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,7 +5,7 @@ import android.hardware.usb.UsbDevice
|
|||
import android.hardware.usb.UsbManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.CoroutineDispatchers
|
||||
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
||||
import com.hoho.android.usbserial.driver.UsbSerialProber
|
||||
|
|
|
@ -3,7 +3,7 @@ package com.geeksville.mesh.service
|
|||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
|
||||
|
||||
class BootCompleteReceiver : BroadcastReceiver(), Logging {
|
||||
|
|
|
@ -6,11 +6,11 @@ import android.content.Intent
|
|||
import android.os.IBinder
|
||||
import android.os.RemoteException
|
||||
import androidx.core.content.edit
|
||||
import com.geeksville.analytics.DataPair
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.android.isGooglePlayAvailable
|
||||
import com.geeksville.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.analytics.DataPair
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.android.isGooglePlayAvailable
|
||||
import com.geeksville.mesh.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.*
|
||||
import com.geeksville.mesh.MeshProtos.MeshPacket
|
||||
import com.geeksville.mesh.MeshProtos.ToRadio
|
||||
|
@ -23,7 +23,7 @@ import com.geeksville.mesh.repository.location.LocationRepository
|
|||
import com.geeksville.mesh.repository.radio.BluetoothInterface
|
||||
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||
import com.geeksville.mesh.repository.radio.RadioServiceConnectionState
|
||||
import com.geeksville.util.*
|
||||
import com.geeksville.mesh.util.*
|
||||
import com.google.protobuf.ByteString
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import dagger.Lazy
|
||||
|
|
|
@ -4,7 +4,7 @@ import android.content.ComponentName
|
|||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.work.*
|
||||
import com.geeksville.andlib.BuildConfig
|
||||
import com.geeksville.mesh.BuildConfig
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,13 +6,13 @@ import android.os.Build
|
|||
import android.os.DeadObjectException
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.concurrent.CallbackContinuation
|
||||
import com.geeksville.concurrent.Continuation
|
||||
import com.geeksville.concurrent.SyncContinuation
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.concurrent.CallbackContinuation
|
||||
import com.geeksville.mesh.concurrent.Continuation
|
||||
import com.geeksville.mesh.concurrent.SyncContinuation
|
||||
import com.geeksville.mesh.android.bluetoothManager
|
||||
import com.geeksville.util.exceptionReporter
|
||||
import com.geeksville.mesh.util.exceptionReporter
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.Closeable
|
||||
import java.util.*
|
||||
|
@ -75,7 +75,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
|||
*/
|
||||
private class BluetoothContinuation(
|
||||
val tag: String,
|
||||
val completion: com.geeksville.concurrent.Continuation<*>,
|
||||
val completion: com.geeksville.mesh.concurrent.Continuation<*>,
|
||||
val timeoutMillis: Long = 0, // If we want to timeout this operation at a certain time, use a non zero value
|
||||
private val startWorkFn: () -> Boolean
|
||||
) : Logging {
|
||||
|
|
|
@ -6,11 +6,11 @@ import android.bluetooth.BluetoothManager
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.app.JobIntentService
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.MainActivity
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.util.exceptionReporter
|
||||
import com.geeksville.mesh.util.exceptionReporter
|
||||
import java.util.*
|
||||
import java.util.zip.CRC32
|
||||
|
||||
|
|
|
@ -6,14 +6,14 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.android.hideKeyboard
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.android.hideKeyboard
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.databinding.AdvancedSettingsBinding
|
||||
import com.geeksville.mesh.model.ChannelOption
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.util.exceptionToSnackbar
|
||||
import com.geeksville.mesh.util.exceptionToSnackbar
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
|
@ -15,10 +15,10 @@ import android.widget.ArrayAdapter
|
|||
import android.widget.ImageView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.geeksville.analytics.DataPair
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.android.hideKeyboard
|
||||
import com.geeksville.mesh.analytics.DataPair
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.android.hideKeyboard
|
||||
import com.geeksville.mesh.AppOnlyProtos
|
||||
import com.geeksville.mesh.ChannelProtos
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
|
|
|
@ -12,7 +12,7 @@ import androidx.fragment.app.activityViewModels
|
|||
import androidx.fragment.app.setFragmentResult
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.databinding.AdapterContactLayoutBinding
|
||||
|
|
|
@ -11,14 +11,14 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.BuildConfig
|
||||
import com.geeksville.mesh.NodeInfo
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.databinding.MapViewBinding
|
||||
import com.geeksville.mesh.model.CustomTileSource
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.util.formatAgo
|
||||
import com.geeksville.mesh.util.formatAgo
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.osmdroid.api.IMapController
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
|
||||
|
||||
object UILog : Logging
|
||||
|
|
|
@ -17,7 +17,7 @@ import androidx.fragment.app.setFragmentResultListener
|
|||
import androidx.lifecycle.asLiveData
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.R
|
||||
|
|
|
@ -7,14 +7,12 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.database.entity.QuickChatAction
|
||||
import com.geeksville.mesh.databinding.QuickChatSettingsFragmentBinding
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
|
||||
/**
|
||||
* A fragment that represents a current 'screen' in our app.
|
||||
|
|
|
@ -12,11 +12,11 @@ import android.view.inputmethod.EditorInfo
|
|||
import android.widget.*
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.geeksville.analytics.DataPair
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.android.hideKeyboard
|
||||
import com.geeksville.android.isGooglePlayAvailable
|
||||
import com.geeksville.mesh.analytics.DataPair
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.android.hideKeyboard
|
||||
import com.geeksville.mesh.android.isGooglePlayAvailable
|
||||
import com.geeksville.mesh.MainActivity
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
|
@ -29,7 +29,7 @@ import com.geeksville.mesh.repository.radio.MockInterface
|
|||
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.service.SoftwareUpdateService
|
||||
import com.geeksville.util.exceptionToSnackbar
|
||||
import com.geeksville.mesh.util.exceptionToSnackbar
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
|
@ -12,13 +12,13 @@ import androidx.fragment.app.activityViewModels
|
|||
import androidx.fragment.app.setFragmentResult
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.NodeInfo
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.databinding.AdapterNodeLayoutBinding
|
||||
import com.geeksville.mesh.databinding.NodelistFragmentBinding
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.util.formatAgo
|
||||
import com.geeksville.mesh.util.formatAgo
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.net.URLEncoder
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package com.geeksville.mesh.util
|
||||
|
||||
import com.geeksville.mesh.BuildConfig
|
||||
|
||||
/// A toString that makes sure all newlines are removed (for nice logging).
|
||||
fun Any.toOneLineString() = this.toString().replace('\n', ' ')
|
||||
|
||||
/// Return a one line string version of an object (but if a release build, just say 'might be PII)
|
||||
fun Any.toPIIString() =
|
||||
if (!BuildConfig.DEBUG)
|
||||
"<PII?>"
|
||||
else
|
||||
this.toOneLineString()
|
||||
|
||||
fun formatAgo(lastSeenUnix: Int): String {
|
||||
val currentTime = (System.currentTimeMillis() / 1000).toInt()
|
||||
val diffMin = (currentTime - lastSeenUnix) / 60;
|
||||
if (diffMin < 1)
|
||||
return "now";
|
||||
if (diffMin < 100)
|
||||
return diffMin.toString() + "m"
|
||||
if (diffMin < 6000)
|
||||
return (diffMin / 60).toString() + "h"
|
||||
if (diffMin < 144000)
|
||||
return (diffMin / (60 * 24)).toString() + "d";
|
||||
return "?";
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package com.geeksville.mesh.util
|
||||
|
||||
fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
|
|
@ -0,0 +1,79 @@
|
|||
package com.geeksville.mesh.util
|
||||
|
||||
import android.os.RemoteException
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
||||
|
||||
object Exceptions : Logging {
|
||||
/// Set in Application.onCreate
|
||||
var reporter: ((Throwable, String?, String?) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* Report an exception to our analytics provider (if installed - otherwise just log)
|
||||
*
|
||||
* After reporting return
|
||||
*/
|
||||
fun report(exception: Throwable, tag: String? = null, message: String? = null) {
|
||||
errormsg(
|
||||
"Exceptions.report: $tag $message",
|
||||
exception
|
||||
) // print the message to the log _before_ telling the crash reporter
|
||||
reporter?.let { r ->
|
||||
r(exception, tag, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This wraps (and discards) exceptions, but first it reports them to our bug tracking system and prints
|
||||
* a message to the log.
|
||||
*/
|
||||
fun exceptionReporter(inner: () -> Unit) {
|
||||
try {
|
||||
inner()
|
||||
} catch (ex: Throwable) {
|
||||
// DO NOT THROW users expect we have fully handled/discarded the exception
|
||||
Exceptions.report(ex, "exceptionReporter", "Uncaught Exception")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If an exception occurs, show the message in a snackbar and continue
|
||||
*/
|
||||
fun exceptionToSnackbar(view: View, inner: () -> Unit) {
|
||||
try {
|
||||
inner()
|
||||
} catch (ex: Throwable) {
|
||||
Snackbar.make(view, ex.message ?: "An exception occurred", Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This wraps (and discards) exceptions, but it does output a log message
|
||||
*/
|
||||
fun ignoreException(silent: Boolean = false, inner: () -> Unit) {
|
||||
try {
|
||||
inner()
|
||||
} catch (ex: Throwable) {
|
||||
// DO NOT THROW users expect we have fully handled/discarded the exception
|
||||
if(!silent)
|
||||
Exceptions.errormsg("ignoring exception", ex)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert any exceptions in this service call into a RemoteException that the client can
|
||||
/// then handle
|
||||
fun <T> toRemoteExceptions(inner: () -> T): T = try {
|
||||
inner()
|
||||
} catch (ex: Throwable) {
|
||||
Log.e("toRemoteExceptions", "Uncaught exception, returning to remote client", ex)
|
||||
when(ex) { // don't double wrap remote exceptions
|
||||
is RemoteException -> throw ex
|
||||
else -> throw RemoteException(ex.message)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.geeksville.mesh.util
|
||||
|
||||
/**
|
||||
* When printing strings to logs sometimes we want to print useful debugging information about users
|
||||
* or positions. But we don't want to leak things like usernames or locations. So this function
|
||||
* if given a string, will return a string which is a maximum of three characters long, taken from the tail
|
||||
* of the string. Which should effectively hide real usernames and locations,
|
||||
* but still let us see if values were zero, empty or different.
|
||||
*/
|
||||
val Any?.anonymize: String
|
||||
get() = this.anonymize()
|
||||
|
||||
/**
|
||||
* A version of anonymize that allows passing in a custom minimum length
|
||||
*/
|
||||
fun Any?.anonymize(maxLen: Int = 3) =
|
||||
if (this != null) ("..." + this.toString().takeLast(maxLen)) else "null"
|
Ładowanie…
Reference in New Issue