Merge pull request #476 from meshtastic/androidlib

incorporate androidlib
pull/477/head
Andre K 2022-09-04 23:25:48 -03:00 zatwierdzone przez GitHub
commit bd541298cd
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
63 zmienionych plików z 1451 dodań i 108 usunięć

Wyświetl plik

@ -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'
}

Wyświetl plik

@ -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.*

Wyświetl plik

@ -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)

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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()
}

Wyświetl plik

@ -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")
}
}

Wyświetl plik

@ -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() {}
}

Wyświetl plik

@ -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() {
}
}
*/

Wyświetl plik

@ -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) }
}
}

Wyświetl plik

@ -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
}
}

Wyświetl plik

@ -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"
}

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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
/**

Wyświetl plik

@ -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())
}
}

Wyświetl plik

@ -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) {
}

Wyświetl plik

@ -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()
}
}

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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()
}
}
}

Wyświetl plik

@ -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
)

Wyświetl plik

@ -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()
}
}

Wyświetl plik

@ -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)
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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
/**

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -1,6 +1,6 @@
package com.geeksville.mesh.repository.radio
import com.geeksville.android.Logging
import com.geeksville.mesh.android.Logging
/**

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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
/**

Wyświetl plik

@ -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

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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

Wyświetl plik

@ -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
/**

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -1,6 +1,6 @@
package com.geeksville.mesh.ui
import com.geeksville.android.Logging
import com.geeksville.mesh.android.Logging
object UILog : Logging

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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.

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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 "?";
}

Wyświetl plik

@ -0,0 +1,3 @@
package com.geeksville.mesh.util
fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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"