util: implement performance monitor; wrap it with a viewmodel

This commit is contained in:
Han Yin 2025-04-11 14:38:38 -07:00
parent 4dd755e25b
commit 46bd638c5f
2 changed files with 309 additions and 0 deletions

View File

@ -0,0 +1,161 @@
package com.example.llama.revamp.monitoring
import android.app.ActivityManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import kotlin.math.roundToInt
/**
* Service that monitors device performance metrics such as memory usage,
* battery level, and temperature.
*/
class PerformanceMonitor(private val context: Context) {
/**
* Provides a flow of memory usage information that updates at the specified interval.
*/
fun monitorMemoryUsage(intervalMs: Long = 5000): Flow<MemoryMetrics> = flow {
while(true) {
emit(getMemoryInfo())
delay(intervalMs)
}
}
/**
* Provides a flow of battery information that updates at the specified interval.
*/
fun monitorBattery(intervalMs: Long = 10000): Flow<BatteryMetrics> = flow {
while(true) {
emit(getBatteryInfo())
delay(intervalMs)
}
}
/**
* Provides a flow of temperature information that updates at the specified interval.
*/
fun monitorTemperature(intervalMs: Long = 10000): Flow<TemperatureMetrics> = flow {
while(true) {
emit(getTemperatureInfo())
delay(intervalMs)
}
}
/**
* Gets the current memory usage information.
*/
private suspend fun getMemoryInfo(): MemoryMetrics = withContext(Dispatchers.IO) {
val mi = ActivityManager.MemoryInfo()
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
activityManager.getMemoryInfo(mi)
val availableMem = mi.availMem
val totalMem = mi.totalMem
val percentUsed = ((totalMem - availableMem) / totalMem.toFloat() * 100).roundToInt()
// Convert to more readable units (GB)
val availableGb = (availableMem / (1024.0 * 1024.0 * 1024.0)).toFloat().round(1)
val totalGb = (totalMem / (1024.0 * 1024.0 * 1024.0)).toFloat().round(1)
MemoryMetrics(
availableMem = availableMem,
totalMem = totalMem,
percentUsed = percentUsed,
availableGb = availableGb,
totalGb = totalGb
)
}
/**
* Gets the current battery information.
*/
private fun getBatteryInfo(): BatteryMetrics {
val intent = context.registerReceiver(null,
IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) ?: 0
val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, 100) ?: 100
val batteryPct = level * 100 / scale
val status = intent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL
return BatteryMetrics(
level = batteryPct,
isCharging = isCharging
)
}
/**
* Gets the current temperature information.
*/
private fun getTemperatureInfo(): TemperatureMetrics {
val intent = context.registerReceiver(null,
IntentFilter(Intent.ACTION_BATTERY_CHANGED))
// Battery temperature is reported in tenths of a degree Celsius
val tempTenthsC = intent?.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0) ?: 0
val tempC = tempTenthsC / 10.0f
val warningLevel = when {
tempC >= 45.0f -> TemperatureWarningLevel.HIGH
tempC >= 40.0f -> TemperatureWarningLevel.MEDIUM
else -> TemperatureWarningLevel.NORMAL
}
return TemperatureMetrics(
temperature = tempC,
warningLevel = warningLevel
)
}
private fun Float.round(decimals: Int): Float {
var multiplier = 1.0f
repeat(decimals) { multiplier *= 10 }
return (this * multiplier).roundToInt() / multiplier
}
}
/**
* Data class containing memory usage metrics.
*/
data class MemoryMetrics(
val availableMem: Long,
val totalMem: Long,
val percentUsed: Int,
val availableGb: Float,
val totalGb: Float
)
/**
* Data class containing battery information.
*/
data class BatteryMetrics(
val level: Int,
val isCharging: Boolean
)
/**
* Warning levels for temperature.
*/
enum class TemperatureWarningLevel {
NORMAL,
MEDIUM,
HIGH
}
/**
* Data class containing temperature information.
*/
data class TemperatureMetrics(
val temperature: Float,
val warningLevel: TemperatureWarningLevel
)

View File

@ -0,0 +1,148 @@
package com.example.llama.revamp.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.example.llama.revamp.data.preferences.UserPreferences
import com.example.llama.revamp.monitoring.BatteryMetrics
import com.example.llama.revamp.monitoring.MemoryMetrics
import com.example.llama.revamp.monitoring.PerformanceMonitor
import com.example.llama.revamp.monitoring.TemperatureMetrics
import com.example.llama.revamp.monitoring.TemperatureWarningLevel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
/**
* ViewModel that manages performance monitoring for the app.
*/
class PerformanceViewModel(
private val performanceMonitor: PerformanceMonitor,
private val userPreferences: UserPreferences
) : ViewModel() {
// Memory usage metrics
private val _memoryUsage = MutableStateFlow(MemoryMetrics(0, 0, 0, 0f, 0f))
val memoryUsage: StateFlow<MemoryMetrics> = _memoryUsage.asStateFlow()
// Battery information
private val _batteryInfo = MutableStateFlow(BatteryMetrics(0, false))
val batteryInfo: StateFlow<BatteryMetrics> = _batteryInfo.asStateFlow()
// Temperature information
private val _temperatureInfo = MutableStateFlow(TemperatureMetrics(0f, TemperatureWarningLevel.NORMAL))
val temperatureInfo: StateFlow<TemperatureMetrics> = _temperatureInfo.asStateFlow()
// User preferences
private val _isMonitoringEnabled = MutableStateFlow(true)
val isMonitoringEnabled: StateFlow<Boolean> = _isMonitoringEnabled.asStateFlow()
private val _useFahrenheitUnit = MutableStateFlow(false)
val useFahrenheitUnit: StateFlow<Boolean> = _useFahrenheitUnit.asStateFlow()
private val _monitoringInterval = MutableStateFlow(5000L)
val monitoringInterval: StateFlow<Long> = _monitoringInterval.asStateFlow()
init {
viewModelScope.launch {
// Load user preferences
_isMonitoringEnabled.value = userPreferences.isPerformanceMonitoringEnabled().first()
_useFahrenheitUnit.value = userPreferences.usesFahrenheitTemperature().first()
_monitoringInterval.value = userPreferences.getMonitoringInterval().first()
// Start monitoring if enabled
if (_isMonitoringEnabled.value) {
startMonitoring()
}
}
}
/**
* Starts monitoring device performance.
*/
private fun startMonitoring() {
val interval = _monitoringInterval.value
viewModelScope.launch {
performanceMonitor.monitorMemoryUsage(interval).collect { metrics ->
_memoryUsage.value = metrics
}
}
viewModelScope.launch {
performanceMonitor.monitorBattery(interval * 2).collect { metrics ->
_batteryInfo.value = metrics
}
}
viewModelScope.launch {
performanceMonitor.monitorTemperature(interval * 2).collect { metrics ->
_temperatureInfo.value = metrics
}
}
}
/**
* Sets whether performance monitoring is enabled.
*/
fun setMonitoringEnabled(enabled: Boolean) {
viewModelScope.launch {
userPreferences.setPerformanceMonitoringEnabled(enabled)
_isMonitoringEnabled.value = enabled
if (enabled && !isMonitoringActive()) {
startMonitoring()
}
}
}
/**
* Sets the temperature unit preference.
*/
fun setUseFahrenheitUnit(useFahrenheit: Boolean) {
viewModelScope.launch {
userPreferences.setUseFahrenheitTemperature(useFahrenheit)
_useFahrenheitUnit.value = useFahrenheit
}
}
/**
* Sets the monitoring interval.
*/
fun setMonitoringInterval(intervalMs: Long) {
viewModelScope.launch {
userPreferences.setMonitoringInterval(intervalMs)
_monitoringInterval.value = intervalMs
// Restart monitoring with new interval if active
if (isMonitoringActive()) {
startMonitoring()
}
}
}
/**
* Checks if monitoring is currently active.
*/
private fun isMonitoringActive(): Boolean {
return _isMonitoringEnabled.value
}
/**
* Factory for creating PerformanceViewModel instances.
*/
class Factory(
private val performanceMonitor: PerformanceMonitor,
private val userPreferences: UserPreferences
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(PerformanceViewModel::class.java)) {
return PerformanceViewModel(performanceMonitor, userPreferences) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
}