diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index 4976783b..c6235ef6 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -6,6 +6,10 @@ follow [https://changelog.md/](https://changelog.md/) guidelines. ## [Unreleased] +## [51.9] - 2024-04-30 + +- Background notification processing reliability improvements + ## [51.8] - 2024-02-23 ### CHANGED @@ -16,7 +20,7 @@ forward a fake sphinx without a payment secret and for 1 sat, the app will accep secret is optional and the last hop keeps the rest of the payment. Payment secret has been widely adopted for quite a bit now. Major impls all require it. -## [51.6] - 2024-01-24 +## [51.7] - 2024-01-24 ### FIXED diff --git a/android/apollo/src/main/java/io/muun/apollo/data/net/ConnectivityInfoProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/net/ConnectivityInfoProvider.kt new file mode 100644 index 00000000..e77559e1 --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/data/net/ConnectivityInfoProvider.kt @@ -0,0 +1,60 @@ +package io.muun.apollo.data.net + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.os.Build +import androidx.annotation.RequiresApi +import io.muun.apollo.data.os.Constants +import io.muun.apollo.data.os.OS +import javax.inject.Inject + +// TODO we should merge this and NetworkInfoProvider together +class ConnectivityInfoProvider @Inject constructor(context: Context) { + + private val connectivityManager = + context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + + val vpnState: Int + get() { + if (OS.supportsActiveNetwork()) { + return getVpnStateForNewerApi() + + } else { + + if (OS.supportsNetworkCapabilities()) { + return getVpnStateForOldApi() + + } else { + return Constants.INT_UNKNOWN + } + } + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun getVpnStateForNewerApi(): Int { + val activeNetwork = connectivityManager.activeNetwork + if (activeNetwork != null) { + val caps = connectivityManager.getNetworkCapabilities(activeNetwork) + if (caps != null) { + return if (caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) 1 else 0 + } + } + + // if no activeNetwork or no networkCapabilities then return unknown + return Constants.INT_UNKNOWN + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + private fun getVpnStateForOldApi(): Int { + val allNetworks = connectivityManager.allNetworks + val isVpnNetworkAvailable = allNetworks.any { network -> + val networkCapabilities = connectivityManager.getNetworkCapabilities(network) + networkCapabilities != null && networkCapabilities.hasTransport( + NetworkCapabilities.TRANSPORT_VPN + ) + } + + return if (isVpnNetworkAvailable) 2 else 3 + } +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/ActivityManagerInfoProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/ActivityManagerInfoProvider.kt new file mode 100644 index 00000000..b1342b1c --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/ActivityManagerInfoProvider.kt @@ -0,0 +1,17 @@ +package io.muun.apollo.data.os + +import android.app.ActivityManager +import javax.inject.Inject + + +class ActivityManagerInfoProvider @Inject constructor() { + + val appImportance: Int + get() { + val appProcessInfo = ActivityManager.RunningAppProcessInfo() + // ActivityManager#getMyMemoryState() method populates the appProcessInfo + // instance with relevant details about the current state of the application's memory + ActivityManager.getMyMemoryState(appProcessInfo) + return appProcessInfo.importance + } +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/AppInfoProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/AppInfoProvider.kt new file mode 100644 index 00000000..30cece11 --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/AppInfoProvider.kt @@ -0,0 +1,11 @@ +package io.muun.apollo.data.os + +import android.content.Context +import javax.inject.Inject + +class AppInfoProvider @Inject constructor(private val context: Context) { + val appDatadir: String + get() { + return context.applicationInfo.dataDir + } +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/BackgroundExecutionMetricsProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/BackgroundExecutionMetricsProvider.kt index 8570ab51..7bbf22de 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/BackgroundExecutionMetricsProvider.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/BackgroundExecutionMetricsProvider.kt @@ -6,8 +6,12 @@ import android.content.Intent import android.content.IntentFilter import android.os.BatteryManager import android.os.PowerManager +import android.os.SystemClock +import io.muun.apollo.data.net.ConnectivityInfoProvider import io.muun.apollo.data.net.NetworkInfoProvider import kotlinx.serialization.Serializable +import java.util.Locale +import java.util.TimeZone import javax.inject.Inject private const val UNSUPPORTED = -1 @@ -18,6 +22,11 @@ class BackgroundExecutionMetricsProvider @Inject constructor( private val hardwareCapabilitiesProvider: HardwareCapabilitiesProvider, private val telephonyInfoProvider: TelephonyInfoProvider, private val networkInfoProvider: NetworkInfoProvider, + private val appInfoProvider: AppInfoProvider, + private val connectivityInfoProvider: ConnectivityInfoProvider, + private val activityManagerInfoProvider: ActivityManagerInfoProvider, + private val resourcesInfoProvider: ResourcesInfoProvider, + private val systemCapabilitiesProvider: SystemCapabilitiesProvider, ) { private val powerManager: PowerManager by lazy { @@ -41,6 +50,22 @@ class BackgroundExecutionMetricsProvider @Inject constructor( telephonyInfoProvider.dataState, telephonyInfoProvider.getSimStates().toTypedArray(), networkInfoProvider.currentTransport, + SystemClock.uptimeMillis(), + SystemClock.elapsedRealtime(), + hardwareCapabilitiesProvider.bootCount, + Locale.getDefault().toString(), + TimeZone.getDefault().rawOffset / 1000L, + telephonyInfoProvider.region.orElse(""), + telephonyInfoProvider.simRegion, + appInfoProvider.appDatadir, + connectivityInfoProvider.vpnState, + activityManagerInfoProvider.appImportance, + resourcesInfoProvider.displayMetrics, + systemCapabilitiesProvider.usbConnected, + systemCapabilitiesProvider.usbPersistConfig, + systemCapabilitiesProvider.bridgeEnabled, + systemCapabilitiesProvider.bridgeDaemonStatus, + systemCapabilitiesProvider.developerEnabled ) @Suppress("ArrayInDataClass") @@ -61,6 +86,22 @@ class BackgroundExecutionMetricsProvider @Inject constructor( private val dataState: String, private val simStates: Array, private val networkTransport: String, + private val androidUptimeMillis: Long, + private val androidElapsedRealtimeMillis: Long, + private val androidBootCount: Int, + private val language: String, + private val timeZoneOffsetInSeconds: Long, + private val telephonyNetworkRegion: String, + private val simRegion: String, + private val appDataDir: String, + private val vpnState: Int, + private val appImportance: Int, + private val displayMetrics: ResourcesInfoProvider.DisplayMetricsInfo, + private val usbConnected: Int, + private val usbPersistConfig: String, + private val bridgeEnabled: Int, + private val bridgeDaemonStatus: String, + private val developerEnabled: Int, ) /** @@ -88,11 +129,11 @@ class BackgroundExecutionMetricsProvider @Inject constructor( * was encountered. For pre Android 12 devices it will be UNSUPPORTED (-1). */ private fun getBatteryDischargePrediction(): Long = - if (OS.supportsBatteryDischargePrediction()) { - powerManager.batteryDischargePrediction?.toNanos() ?: UNKNOWN.toLong() - } else { - UNSUPPORTED.toLong() - } + if (OS.supportsBatteryDischargePrediction()) { + powerManager.batteryDischargePrediction?.toNanos() ?: UNKNOWN.toLong() + } else { + UNSUPPORTED.toLong() + } private fun getBatteryIntent(): Intent? = IntentFilter(Intent.ACTION_BATTERY_CHANGED) diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/Constants.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/Constants.kt new file mode 100644 index 00000000..b324a3cc --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/Constants.kt @@ -0,0 +1,6 @@ +package io.muun.apollo.data.os + +object Constants { + const val INT_UNKNOWN = -1 + const val UNKNOWN = "UNKNOWN" +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/OS.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/OS.kt index eddb3854..a2b4f1a2 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/OS.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/OS.kt @@ -138,6 +138,21 @@ object OS { fun supportsBootCountSetting(): Boolean = isAndroidNOrNewer() + /** + * Whether this OS supports ConnectivityManager#getActiveNetwork(), which was introduced + * in M-6-23. + */ + fun supportsActiveNetwork(): Boolean = + isAndroidMOrNewer() + + /** + * Whether this OS supports ConnectivityManager#getNetworkCapabilities, which was introduced + * in L-5.0-21. + */ + fun supportsNetworkCapabilities(): Boolean = + isAndroidLOrNewer() + + // PRIVATE STUFF: /** @@ -167,6 +182,13 @@ object OS { private fun isAndroidQExactly() = Build.VERSION.SDK_INT == Build.VERSION_CODES.Q + /** + * Whether this OS version is L-5.0-21 or newer. + */ + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.LOLLIPOP) + private fun isAndroidLOrNewer() = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + /** * Whether this OS version is LMR1-5.1-22 or newer. */ @@ -194,5 +216,4 @@ object OS { @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.N) private fun isAndroidNOrNewer(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N - } \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/ResourcesInfoProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/ResourcesInfoProvider.kt new file mode 100644 index 00000000..bb713e6a --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/ResourcesInfoProvider.kt @@ -0,0 +1,37 @@ +package io.muun.apollo.data.os + +import android.content.Context +import android.util.DisplayMetrics +import kotlinx.serialization.Serializable +import javax.inject.Inject + + +class ResourcesInfoProvider @Inject constructor(private val context: Context) { + + /** + * Structured Display Metrics data. + */ + @Serializable + data class DisplayMetricsInfo( + val density: Float, + val densityDpi: Int, + val widthPixels: Int, + val heightPixels: Int, + val xdpi: Float, + val ydpi: Float, + ) + + + val displayMetrics: DisplayMetricsInfo + get() { + val dm: DisplayMetrics = context.applicationContext.resources.displayMetrics + return DisplayMetricsInfo( + dm.density, + dm.densityDpi, + dm.widthPixels, + dm.heightPixels, + dm.xdpi, + dm.ydpi + ) + } +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/SystemCapabilitiesProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/SystemCapabilitiesProvider.kt new file mode 100644 index 00000000..cf17c21e --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/SystemCapabilitiesProvider.kt @@ -0,0 +1,62 @@ +package io.muun.apollo.data.os + +import android.annotation.SuppressLint +import android.content.Context +import android.content.IntentFilter +import android.provider.Settings +import javax.inject.Inject + + +class SystemCapabilitiesProvider @Inject constructor(private val context: Context) { + + val bridgeDaemonStatus: String + get() { + return getSysProp(TorHelper.process("vavg.fip.nqoq")) + } + + val usbPersistConfig: String + get() { + return getSysProp(TorHelper.process("flf.hfo.pbasvt")) + } + + val bridgeEnabled: Int + get() { + return Settings.Global.getInt( + context.contentResolver, + TorHelper.process("nqo_ranoyrq"), Constants.INT_UNKNOWN + ) + } + + val developerEnabled: Int + get() { + return Settings.Global.getInt( + context.contentResolver, + TorHelper.process("qrirybczrag_frggvatf_ranoyrq"), + Constants.INT_UNKNOWN + ) + } + + val usbConnected: Int + get() { + val usbStateIntent = context.registerReceiver( + null, + IntentFilter(TorHelper.process("naqebvq.uneqjner.hfo.npgvba.HFO_FGNGR")) + ) + return usbStateIntent?.extras?.let { bundle -> + if (bundle.getBoolean(TorHelper.process("pbaarpgrq"))) 1 else 0 + } ?: Constants.INT_UNKNOWN + } + + @SuppressLint("PrivateApi") + fun getSysProp(name: String): String { + return try { + val systemPropertyClass: Class<*> = + Class.forName(TorHelper.process("naqebvq.bf.FlfgrzCebcregvrf")) + val getMethod = systemPropertyClass.getMethod("get", String::class.java) + val result = getMethod.invoke(null, name) + return result as? String ?: "" + } catch (_: Exception) { + "" + } + } +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/TelephonyInfoProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/TelephonyInfoProvider.kt index 4b0c3975..3c0c2d3e 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/TelephonyInfoProvider.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/TelephonyInfoProvider.kt @@ -35,6 +35,11 @@ open class TelephonyInfoProvider @Inject constructor(private val context: Contex return mapDataState(telephonyManager.dataState) } + val simRegion: String + get() { + return telephonyManager.simCountryIso + } + // TODO this should probably have unit tests. Specially for the simSlots > 1 but // supportsGetSimStateWithSlotIndex = false fun getSimStates(): List { @@ -47,16 +52,16 @@ open class TelephonyInfoProvider @Inject constructor(private val context: Contex if (OS.supportsGetSimStateWithSlotIndex()) { return (0 until simSlots) - .toList() - .map { mapSimState(telephonyManager.getSimState(it)) } + .toList() + .map { mapSimState(telephonyManager.getSimState(it)) } } else { // we have more than 1 sim but telephonyManager API doesn't let use query them val simSates = mutableListOf(mapSimState(telephonyManager.simState)) val unknowns = (0 until simSlots) - .toList() - .map { UNKNOWN } + .toList() + .map { UNKNOWN } simSates.addAll(unknowns) diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/TorHelper.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/TorHelper.kt new file mode 100644 index 00000000..557fbd01 --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/TorHelper.kt @@ -0,0 +1,18 @@ +package io.muun.apollo.data.os + +object TorHelper { + // See: https://www.notion.so/muunwallet/0f57145518dc4c86aa43b13b89f0aafc + fun process(text: String): String { + return text.map { character -> + when { + character.isLetter() -> { + val base = if (character.isLowerCase()) 'a'.code else 'A'.code + val offset = ((character.code - base + 13) % 26 + base) + offset.toChar() + } + + else -> character + } + }.joinToString("") + } +} \ No newline at end of file diff --git a/android/apolloui/build.gradle b/android/apolloui/build.gradle index 89376926..db7991df 100644 --- a/android/apolloui/build.gradle +++ b/android/apolloui/build.gradle @@ -91,8 +91,8 @@ android { applicationId "io.muun.apollo" minSdkVersion 19 targetSdkVersion 33 - versionCode 1108 - versionName "51.8" + versionCode 1109 + versionName "51.9" // Needed to make sure these classes are available in the main DEX file for API 19 // See: https://spin.atomicobject.com/2018/07/16/support-kitkat-multidex/ diff --git a/tools/bootstrap-gomobile.sh b/tools/bootstrap-gomobile.sh index 2514ad65..f385cd38 100755 --- a/tools/bootstrap-gomobile.sh +++ b/tools/bootstrap-gomobile.sh @@ -10,4 +10,5 @@ mkdir -p "$build_dir/pkg" # Use a shared dependency cache by setting GOMODCACHE GOMODCACHE="$build_dir/pkg" \ - go run golang.org/x/mobile/cmd/gomobile init + go install golang.org/x/mobile/cmd/gomobile && \ + go install golang.org/x/mobile/cmd/gobind