diff --git a/app/build.gradle b/app/build.gradle index f22a408f..79b83393 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' } diff --git a/app/src/main/java/com/geeksville/mesh/BaseActivity.kt b/app/src/main/java/com/geeksville/mesh/BaseActivity.kt index 205a20b2..b61fbd3c 100644 --- a/app/src/main/java/com/geeksville/mesh/BaseActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/BaseActivity.kt @@ -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.* diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index f78f4fe8..1ddf491d 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -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) diff --git a/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt b/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt index 76d2d9ed..fc983923 100644 --- a/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt +++ b/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt index e533c197..26ac5857 100644 --- a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/analytics/AnalyticsClient.kt b/app/src/main/java/com/geeksville/mesh/analytics/AnalyticsClient.kt new file mode 100644 index 00000000..9a0cb40f --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/analytics/AnalyticsClient.kt @@ -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() + +} + + diff --git a/app/src/main/java/com/geeksville/mesh/analytics/FirebaseAnalytics.kt b/app/src/main/java/com/geeksville/mesh/analytics/FirebaseAnalytics.kt new file mode 100644 index 00000000..983dda0c --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/analytics/FirebaseAnalytics.kt @@ -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") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/analytics/MixpanelAnalytics.kt b/app/src/main/java/com/geeksville/mesh/analytics/MixpanelAnalytics.kt new file mode 100644 index 00000000..b8b4d9b1 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/analytics/MixpanelAnalytics.kt @@ -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) = + 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() {} +} + diff --git a/app/src/main/java/com/geeksville/mesh/analytics/SplunkAnalytics.kt b/app/src/main/java/com/geeksville/mesh/analytics/SplunkAnalytics.kt new file mode 100644 index 00000000..2073a371 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/analytics/SplunkAnalytics.kt @@ -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() { + } +} + + */ \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/analytics/TeeAnalytics.kt b/app/src/main/java/com/geeksville/mesh/analytics/TeeAnalytics.kt new file mode 100644 index 00000000..59e97592 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/analytics/TeeAnalytics.kt @@ -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) } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/android/AppPrefs.kt b/app/src/main/java/com/geeksville/mesh/android/AppPrefs.kt new file mode 100644 index 00000000..dc147bc6 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/android/AppPrefs.kt @@ -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 = thisRef.getPrefs().getFloat(thisRef.makeName(prop.name), java.lang.Float.MIN_VALUE) + + fun set(thisRef: AppPrefs, prop: KProperty, 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 = thisRef.getPrefs().getString(thisRef.makeName(prop.name), default)!! + + fun set(thisRef: AppPrefs, prop: KProperty, 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) { + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/android/BuildUtils.kt b/app/src/main/java/com/geeksville/mesh/android/BuildUtils.kt new file mode 100644 index 00000000..93cb401b --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/android/BuildUtils.kt @@ -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" +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/android/ContextExtensions.kt b/app/src/main/java/com/geeksville/mesh/android/ContextExtensions.kt new file mode 100644 index 00000000..219585f3 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/android/ContextExtensions.kt @@ -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) + } +} diff --git a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt index 098b6853..42422999 100644 --- a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt +++ b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt @@ -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 /** diff --git a/app/src/main/java/com/geeksville/mesh/android/DateUtils.kt b/app/src/main/java/com/geeksville/mesh/android/DateUtils.kt new file mode 100644 index 00000000..ededfa21 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/android/DateUtils.kt @@ -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()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/android/DebugLogFile.kt b/app/src/main/java/com/geeksville/mesh/android/DebugLogFile.kt new file mode 100644 index 00000000..c5b68e4b --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/android/DebugLogFile.kt @@ -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) { + +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/android/ExpireChecker.kt b/app/src/main/java/com/geeksville/mesh/android/ExpireChecker.kt new file mode 100644 index 00000000..3cb21189 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/android/ExpireChecker.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/android/GeeksvilleApplication.kt b/app/src/main/java/com/geeksville/mesh/android/GeeksvilleApplication.kt new file mode 100644 index 00000000..a0625db0 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/android/GeeksvilleApplication.kt @@ -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.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 + +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/android/Logging.kt b/app/src/main/java/com/geeksville/mesh/android/Logging.kt new file mode 100644 index 00000000..036afb7a --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/android/Logging.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/android/PlayClient.kt b/app/src/main/java/com/geeksville/mesh/android/PlayClient.kt new file mode 100644 index 00000000..27b55714 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/android/PlayClient.kt @@ -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>, scopes: Array = 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) + } +} diff --git a/app/src/main/java/com/geeksville/mesh/android/ServiceClient.kt b/app/src/main/java/com/geeksville/mesh/android/ServiceClient.kt new file mode 100644 index 00000000..daebfd5b --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/android/ServiceClient.kt @@ -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(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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/concurrent/Coroutines.kt b/app/src/main/java/com/geeksville/mesh/concurrent/Coroutines.kt new file mode 100644 index 00000000..89c652b2 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/concurrent/Coroutines.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/concurrent/DeferredExecution.kt b/app/src/main/java/com/geeksville/mesh/concurrent/DeferredExecution.kt new file mode 100644 index 00000000..d6f061e3 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/concurrent/DeferredExecution.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/concurrent/SyncContinuation.kt b/app/src/main/java/com/geeksville/mesh/concurrent/SyncContinuation.kt new file mode 100644 index 00000000..3e270a5f --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/concurrent/SyncContinuation.kt @@ -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 : Logging { + abstract fun resume(res: Result) + + // 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(private val cb: (Result) -> Unit) : Continuation { + override fun resume(res: Result) = 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 : Continuation { + + private val mbox = java.lang.Object() + private var result: Result? = null + + override fun resume(res: Result) { + 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 suspend(timeoutMsecs: Long = -1, initfn: (SyncContinuation) -> Unit): T { + val cont = SyncContinuation() + + // First call the init funct + initfn(cont) + + // Now wait for the continuation to finish + return cont.await(timeoutMsecs) +} diff --git a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt index 4e6a71b8..f09f23d5 100644 --- a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt b/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt index e41ec928..298f28cd 100644 --- a/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt +++ b/app/src/main/java/com/geeksville/mesh/model/ChannelSet.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/model/DeviceVersion.kt b/app/src/main/java/com/geeksville/mesh/model/DeviceVersion.kt index d23574f4..09ba6fcf 100644 --- a/app/src/main/java/com/geeksville/mesh/model/DeviceVersion.kt +++ b/app/src/main/java/com/geeksville/mesh/model/DeviceVersion.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt b/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt index 5ec87d59..4343adde 100644 --- a/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index 741876ea..5b9d8b6a 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothBroadcastReceiver.kt b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothBroadcastReceiver.kt index 3f4c2ee7..3c6ee25e 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothBroadcastReceiver.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothBroadcastReceiver.kt @@ -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 /** diff --git a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt index 0188eaf6..bd0c34ed 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/repository/datastore/LocalConfigRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/datastore/LocalConfigRepository.kt index 42912569..4553a59c 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/datastore/LocalConfigRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/datastore/LocalConfigRepository.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/repository/location/SharedLocationManager.kt b/app/src/main/java/com/geeksville/mesh/repository/location/SharedLocationManager.kt index 1679f36a..96aa1907 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/location/SharedLocationManager.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/location/SharedLocationManager.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepository.kt index 5352c66d..384afe86 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/nsd/NsdRepository.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/BluetoothInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/BluetoothInterface.kt index 869dda64..00ca34e6 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/BluetoothInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/BluetoothInterface.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/MockInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/MockInterface.kt index d2b27774..5ddaa07c 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/MockInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/MockInterface.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/NopInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/NopInterface.kt index a7cc5350..84301820 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/NopInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/NopInterface.kt @@ -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 { diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt index 897bfd94..7a81d528 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/SerialInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/SerialInterface.kt index 4e97f58c..58f8b645 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/SerialInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/SerialInterface.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/StreamInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/StreamInterface.kt index b9c0aa4d..bbe862db 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/StreamInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/StreamInterface.kt @@ -1,6 +1,6 @@ package com.geeksville.mesh.repository.radio -import com.geeksville.android.Logging +import com.geeksville.mesh.android.Logging /** diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt index d33f1c5a..ee368063 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/repository/usb/SerialConnectionImpl.kt b/app/src/main/java/com/geeksville/mesh/repository/usb/SerialConnectionImpl.kt index 4ab92961..271a5ff2 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/usb/SerialConnectionImpl.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/usb/SerialConnectionImpl.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/repository/usb/UsbBroadcastReceiver.kt b/app/src/main/java/com/geeksville/mesh/repository/usb/UsbBroadcastReceiver.kt index b8f895f8..47f7a2b2 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/usb/UsbBroadcastReceiver.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/usb/UsbBroadcastReceiver.kt @@ -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 /** diff --git a/app/src/main/java/com/geeksville/mesh/repository/usb/UsbRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/usb/UsbRepository.kt index 1d4cbc6f..3a0bec1c 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/usb/UsbRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/usb/UsbRepository.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/service/BootCompleteReceiver.kt b/app/src/main/java/com/geeksville/mesh/service/BootCompleteReceiver.kt index 4cbcd2c3..089c574e 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BootCompleteReceiver.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BootCompleteReceiver.kt @@ -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 { diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 0517cc62..1a91181b 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshServiceStarter.kt b/app/src/main/java/com/geeksville/mesh/service/MeshServiceStarter.kt index f63b28ec..729f6772 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshServiceStarter.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshServiceStarter.kt @@ -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 /** diff --git a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt index 5dc982ab..39a47852 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt @@ -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 { diff --git a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt index 0eba401e..3e76e072 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt index 501bed19..1539459a 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt index 8a16011f..666b14fa 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt index ec8fdf1f..d942a476 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt index 92d8de76..2fcc2f9c 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt b/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt index 7a8da82d..f54648d9 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt @@ -1,6 +1,6 @@ package com.geeksville.mesh.ui -import com.geeksville.android.Logging +import com.geeksville.mesh.android.Logging object UILog : Logging diff --git a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt index b72d209e..1ce870a7 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/ui/QuickChatSettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/QuickChatSettingsFragment.kt index 56c53ed3..2945bbc8 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/QuickChatSettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/QuickChatSettingsFragment.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/ui/ScreenFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ScreenFragment.kt index f02a4991..21798b84 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ScreenFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ScreenFragment.kt @@ -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. diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index 9e5c6aa4..fab749ea 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index 8e20da53..55bdc364 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/util/AnyExtensions.kt b/app/src/main/java/com/geeksville/mesh/util/AnyExtensions.kt new file mode 100644 index 00000000..db7e0929 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/util/AnyExtensions.kt @@ -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) + "" + 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 "?"; +} diff --git a/app/src/main/java/com/geeksville/mesh/util/ByteArrayExtensions.kt b/app/src/main/java/com/geeksville/mesh/util/ByteArrayExtensions.kt new file mode 100644 index 00000000..21fb37b8 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/util/ByteArrayExtensions.kt @@ -0,0 +1,3 @@ +package com.geeksville.mesh.util + +fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/util/Exceptions.kt b/app/src/main/java/com/geeksville/mesh/util/Exceptions.kt new file mode 100644 index 00000000..690574a9 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/util/Exceptions.kt @@ -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 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) + } +} + diff --git a/app/src/main/java/com/geeksville/mesh/util/anonymize.kt b/app/src/main/java/com/geeksville/mesh/util/anonymize.kt new file mode 100644 index 00000000..7283946e --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/util/anonymize.kt @@ -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" \ No newline at end of file