Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
### Requirements

* [OpenJDK](https://adoptopenjdk.net/?variant=openjdk11&jvmVariant=hotspot) >= 8
* [Golang](https://golang.org/dl/) >= 1.16
* [Android NDK](https://developer.android.com/ndk/downloads) >= 21
* [Golang](https://golang.org/dl/) = 1.24.x
* [Android NDK](https://developer.android.com/ndk/downloads) >= 22
* [Docker](https://docs.docker.com/engine/install/) >= 28

### Instructions
Expand All @@ -28,7 +28,7 @@

### Instructions

1. Ensure Docker has at least 5 GB of RAM and run:
1. Ensure Docker has at least 16 GB of RAM, 60 GB of free space on disk and run:
```shell
mkdir -p apk
DOCKER_BUILDKIT=1 docker build -f android/Dockerfile -o apk .
Expand All @@ -49,7 +49,7 @@
adb pull $(adb shell pm path io.muun.apollo | grep "/base.apk" | sed 's/^package://') apollo-play.apk
```
2. Checkout the commit that corresponds to the version of the app you want to verify.
3. Ensure Docker has at least 5 GB of RAM and run:
3. Ensure Docker has at least 16 GB of RAM, 60 GB of free space on disk and run:
```shell
tools/verify-apollo.sh <path-to-verify.apk>
```
Expand Down
17 changes: 16 additions & 1 deletion android/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,22 @@ follow [https://changelog.md/](https://changelog.md/) guidelines.

## [Unreleased]

## [55.1] - 2025-09-23
## [55.3] - 2025-10-08

### CHANGED

- Bump `targetSDK` to 35
- Replace ButterKnife with ViewBinding in various screens
- Migrate ApolloApplication to Kotlin
- Migrate Dagger @Component to Kotlin
- Migrate Dagger @Module to Kotlin
- Update go version to 1.24 to support 16kb pages

### ADDED

- 16kb page support

## [55.2] - 2025-09-23

### FIXED

Expand Down
7 changes: 6 additions & 1 deletion android/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# VERY IMPORTANT!!!
# This file is used by build-release.yml (https://github.com/muun/apollo/blob/eedb679b9a5b7af2c617cc794c8dcf53eb1b4321/.github/workflows/build-release.yml).
# Any change made here will affect how the APK is built in the OSS repository (github action)
# and will impact the reproducible build verification.

FROM --platform=linux/amd64 openjdk:17-jdk-slim@sha256:aaa3b3cb27e3e520b8f116863d0580c438ed55ecfa0bc126b41f68c3f62f9774 AS muun_android_builder

ENV NDK_VERSION 22.0.7026061
ENV ANDROID_PLATFORM_VERSION 28
ENV ANDROID_BUILD_TOOLS_VERSION 28.0.3
ENV GO_VERSION 1.22.1
ENV GO_VERSION 1.24.7

RUN apt-get update \
&& apt-get install --yes --no-install-recommends \
Expand Down
16 changes: 9 additions & 7 deletions android/apolloui/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ android {
defaultConfig {
applicationId "io.muun.apollo"
minSdk 19
targetSdk 34
versionCode 1502
versionName "55.2"
targetSdk 35
versionCode 1503
versionName "55.3"

// 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/
Expand Down Expand Up @@ -455,7 +455,8 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1"

// Detect root devices (for error reports metadata and fraud control)
implementation 'com.scottyab:rootbeer-lib:0.1.0'
// TODO: go back to `com.scottyab:rootbeer-lib:x.x.x` after dropping API 19 support
implementation 'com.github.muun:rootbeer:0.1.0'

// Google Play Integrity
implementation 'com.google.android.play:integrity:1.1.0'
Expand All @@ -481,16 +482,17 @@ dependencies {
kapt "com.google.dagger:dagger-compiler:$global_version_dagger"

// Android support:
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.annotation:annotation:1.0.0'
implementation 'androidx.preference:preference:1.0.0'
def emojiVersion = "1.1.0" // We need targetSdkVersion 32 to bump
implementation "androidx.emoji2:emoji2:$emojiVersion"
implementation "androidx.activity:activity:1.8.2"

// Remove gridrlayout-v7 once we drop minSDK version 19
// Remove gridlayout-v7 once we drop minSDK version 19
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.multidex:multidex:2.0.0'
// We need to specify versions for customtabs and cardview because some dependencies use them
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,6 @@ class ActivityManagerInfoProvider(context: Context) {
private val activityManager: ActivityManager =
context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager

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
}

/**
* Returns true if this is a low-RAM device. Exactly whether a device is low-RAM is ultimately
* up to the device configuration, but currently it generally means something with 1GB or less
Expand Down Expand Up @@ -62,11 +53,6 @@ class ActivityManagerInfoProvider(context: Context) {
}
}

val isUserAMonkey: Boolean
get() {
return ActivityManager.isUserAMonkey()
}

val isLowMemoryKillReportSupported: Boolean
get() {
return if (OS.supportsLowMemoryKillReport()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,10 @@ class BackgroundExecutionMetricsProvider @Inject constructor(
BackgroundExecutionMetrics(
metricsProvider.currentTimeMillis,
metricsProvider.batteryLevel,
metricsProvider.maxBatteryLevel,
metricsProvider.batteryHealth,
metricsProvider.batteryDischargePrediction,
metricsProvider.batteryStatus,
metricsProvider.totalInternalStorageInBytes,
metricsProvider.freeInternalStorageInBytes,
metricsProvider.freeExternalStorageInBytes.toTypedArray(),
metricsProvider.totalExternalStorageInBytes.toTypedArray(),
metricsProvider.totalRamInBytes,
metricsProvider.freeRamInBytes,
metricsProvider.dataState,
metricsProvider.simStates.toTypedArray(),
metricsProvider.currentNetworkTransport,
Expand All @@ -34,8 +28,6 @@ class BackgroundExecutionMetricsProvider @Inject constructor(
metricsProvider.simRegion,
metricsProvider.appDatadir,
metricsProvider.vpnState,
metricsProvider.appImportance,
metricsProvider.displayMetrics,
metricsProvider.usbConnected,
metricsProvider.usbPersistConfig,
metricsProvider.bridgeEnabled,
Expand All @@ -47,16 +39,11 @@ class BackgroundExecutionMetricsProvider @Inject constructor(
metricsProvider.autoDateTime,
metricsProvider.autoTimeZone,
metricsProvider.timeZoneId,
metricsProvider.dateFormat,
metricsProvider.regionCode,
metricsProvider.calendarIdentifier,
metricsProvider.androidMobileRxTraffic,
metricsProvider.simOperatorId,
metricsProvider.mobileNetworkId,
metricsProvider.mobileRoaming,
metricsProvider.mobileDataStatus,
metricsProvider.mobileRadioType,
metricsProvider.mobileDataActivity,
metricsProvider.networkLink,
metricsProvider.hasNfcFeature,
metricsProvider.hasNfcAdapter,
Expand All @@ -66,24 +53,20 @@ class BackgroundExecutionMetricsProvider @Inject constructor(
metricsProvider.isDeviceFoldable,
metricsProvider.isBackgroundRestricted,
metricsProvider.latestBackgroundTimes,
metricsProvider.telephonyNetworkRegionList
metricsProvider.internalLevel.let { "${it.first};${it.second}" },
metricsProvider.batteryRemainState,
metricsProvider.isCharging
)

@Suppress("ArrayInDataClass")
@Serializable
data class BackgroundExecutionMetrics(
private val epochInMilliseconds: Long,
private val batteryLevel: Int,
private val maxBatteryLevel: Int,
private val batteryHealth: String,
private val batteryDischargePrediction: Long?,
private val batteryState: String,
private val totalInternalStorage: Long,
private val freeInternalStorage: Long,
private val freeExternalStorage: Array<Long>,
private val totalExternalStorage: Array<Long>,
private val totalRamStorage: Long,
private val freeRamStorage: Long,
private val dataState: String,
private val simStates: Array<String>,
private val networkTransport: String,
Expand All @@ -96,8 +79,6 @@ class BackgroundExecutionMetricsProvider @Inject constructor(
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,
Expand All @@ -109,16 +90,11 @@ class BackgroundExecutionMetricsProvider @Inject constructor(
private val autoDateTime: Int,
private val autoTimeZone: Int,
private val timeZoneId: String,
private val androidDateFormat: String,
private val regionCode: String,
private val androidCalendarIdentifier: String,
private val androidMobileRxTraffic: Long,
private val androidSimOperatorId: String,
private val androidMobileOperatorId: String,
private val androidMobileRoaming: Boolean,
private val androidMobileDataStatus: Int,
private val androidMobileRadioType: Int,
private val androidMobileDataActivity: Int,
private val androidNetworkLink: ConnectivityInfoProvider.NetworkLink?,
private val androidHasNfcFeature: Boolean,
private val androidHasNfcAdapter: Boolean,
Expand All @@ -128,6 +104,8 @@ class BackgroundExecutionMetricsProvider @Inject constructor(
private val androidFoldableDevice: Boolean?,
private val isBackgroundRestricted: Boolean,
private val bkgTimes: List<BackgroundEvent>,
private val telephonyNetworkRegionList: List<String>,
private val internalLevel: String,
private val batteryRemainState: String,
private val isCharging: Boolean?
)
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
package io.muun.apollo.data.afs

import android.content.Context
import android.content.Context.POWER_SERVICE
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.PowerManager
import io.muun.apollo.data.os.OS

private const val UNSUPPORTED = -1
private const val UNKNOWN = -2

class BatteryInfoProvider(private val context: Context) {

private val powerManager: PowerManager by lazy {
context.getSystemService(POWER_SERVICE) as PowerManager
context.getSystemService(Context.POWER_SERVICE) as PowerManager
}

/**
* Returns the device battery health, which will be a string constant representing the general
* health of this device. Note: Android docs do not explain what these values exactly mean.
*/
val batteryHealth: String
get() = getBatteryHealthText(getBatteryProperty(BatteryManager.EXTRA_HEALTH))
private val batteryManager: BatteryManager by lazy {
context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
}

/**
* Returns the device battery status, which will be a string constant with one of the following
Expand All @@ -36,16 +29,28 @@ class BatteryInfoProvider(private val context: Context) {
val batteryStatus: String
get() = getBatteryStatusText(getBatteryProperty(BatteryManager.EXTRA_STATUS))

/**
* (Android only and Android 12+ only) Returns the current battery life remaining estimate,
* expressed in nanoseconds. Will be UNKNOWN (-2) if the device is powered, charging, or an error
* was encountered. For pre Android 12 devices it will be UNSUPPORTED (-1).
*/
val batteryDischargePrediction: Long
get() = if (OS.supportsBatteryDischargePrediction()) {
powerManager.batteryDischargePrediction?.toNanos() ?: UNKNOWN.toLong()
val isCharging: Boolean?
get() = if (OS.supportsBatteryManagerIsCharging()) {
batteryManager.isCharging
} else {
UNSUPPORTED.toLong()
null
}

val batteryRemainState: String
get() {
if (!OS.supportsBatteryDischargePrediction()) {
return Constants.UNKNOWN
}
val prediction = powerManager.batteryDischargePrediction?.toNanos()
if (prediction == null) {
return "CHARGING"
}

return when {
prediction < 0 -> "NEGATIVE"
prediction.toInt() == 0 -> "ZERO"
else -> "POSITIVE"
}
}

/**
Expand All @@ -54,12 +59,6 @@ class BatteryInfoProvider(private val context: Context) {
val batteryLevel: Int
get() = getBatteryProperty(BatteryManager.EXTRA_LEVEL)

/**
* Returns an integer representing the maximum battery level.
*/
val maxBatteryLevel: Int
get() = getBatteryProperty(BatteryManager.EXTRA_SCALE)

private fun getBatteryIntent(): Intent? =
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
.let { intentFilter ->
Expand All @@ -69,19 +68,6 @@ class BatteryInfoProvider(private val context: Context) {
private fun getBatteryProperty(propertyName: String) =
getBatteryIntent()?.getIntExtra(propertyName, -1) ?: -1

private fun getBatteryHealthText(batteryHealth: Int): String {
return when (batteryHealth) {
BatteryManager.BATTERY_HEALTH_COLD -> "COLD"
BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE -> "UNSPECIFIED_FAILURE"
BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE -> "OVER_VOLTAGE"
BatteryManager.BATTERY_HEALTH_DEAD -> "DEAD"
BatteryManager.BATTERY_HEALTH_OVERHEAT -> "OVERHEAT"
BatteryManager.BATTERY_HEALTH_GOOD -> "GOOD"
BatteryManager.BATTERY_HEALTH_UNKNOWN -> "UNKNOWN"
else -> "UNREADABLE"
}
}

/**
* Translate Android's BatteryManager battery status int constants into one of our domain
* values. Note that Android docs don't really explain what these values mean.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@ import kotlinx.serialization.Serializable
@Serializable
data class BuildInfo(
val abis: List<String>,
val fingerprint: String,
val hardware: String,
val bootloader: String,
val manufacturer: String,
val brand: String,
val display: String,
val time: Long,
val host: String,
val type: String,
val radioVersion: String?,
val securityPatch: String,
val baseOs: String,
// These fields are null on older app versions
val model: String?,
val product: String?,
val release: String?
)
Loading