diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 64047bbc1..85d3c35b1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -21,13 +21,30 @@ android { applicationId = "com.sameerasw.essentials" minSdk = 26 targetSdk = 36 - versionCode = 29 - versionName = "11.5" + versionCode = 30 + versionName = "11.6" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { + +// optimized dev build + +// debug { +// isMinifyEnabled = true +// isShrinkResources = true +// isDebuggable = false +// +// proguardFiles( +// getDefaultProguardFile("proguard-android-optimize.txt"), +// "proguard-rules.pro" +// ) +// } + + // end + + release { isMinifyEnabled = true isShrinkResources = true diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index ef42cdef8..6c52fb89e 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -32,6 +32,7 @@ # Kotlin Reflect -keep class kotlin.reflect.** { *; } -keep class com.sameerasw.essentials.domain.model.** { *; } +-keep class com.sameerasw.essentials.domain.diy.** { *; } # Prevent over-minification of settings and registry classes -keep class com.sameerasw.essentials.data.repository.** { *; } diff --git a/app/src/main/java/com/sameerasw/essentials/data/repository/GitHubRepository.kt b/app/src/main/java/com/sameerasw/essentials/data/repository/GitHubRepository.kt index 125997c36..2bc3b06c1 100644 --- a/app/src/main/java/com/sameerasw/essentials/data/repository/GitHubRepository.kt +++ b/app/src/main/java/com/sameerasw/essentials/data/repository/GitHubRepository.kt @@ -70,9 +70,7 @@ class GitHubRepository { } if (connection.responseCode == 200) { val data = connection.inputStream.bufferedReader().readText() - val listType = - object : com.google.gson.reflect.TypeToken>() {}.type - gson.fromJson(data, listType) + gson.fromJson(data, Array::class.java).toList() } else if (connection.responseCode == 403 || connection.responseCode == 429) { throw Exception("RATE_LIMIT") } else emptyList() diff --git a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt index a5975de12..f39f0ecee 100644 --- a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt +++ b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt @@ -3,7 +3,6 @@ package com.sameerasw.essentials.data.repository import android.content.Context import android.content.SharedPreferences import com.google.gson.Gson -import com.google.gson.reflect.TypeToken import com.sameerasw.essentials.domain.HapticFeedbackType import com.sameerasw.essentials.domain.model.AppSelection import com.sameerasw.essentials.domain.model.NotificationLightingColorMode @@ -247,9 +246,8 @@ class SettingsRepository(private val context: Context) { fun getNotificationLightingGlowSides(): Set { val json = prefs.getString(KEY_EDGE_LIGHTING_GLOW_SIDES, null) return if (json != null) { - val type = object : TypeToken>() {}.type try { - gson.fromJson(json, type) + gson.fromJson(json, Array::class.java).toSet() } catch (e: Exception) { setOf(NotificationLightingSide.LEFT, NotificationLightingSide.RIGHT) } @@ -267,7 +265,7 @@ class SettingsRepository(private val context: Context) { val json = prefs.getString(KEY_FREEZE_AUTO_EXCLUDED_APPS, null) return if (json != null) { try { - gson.fromJson(json, object : TypeToken>() {}.type) ?: emptySet() + gson.fromJson(json, Array::class.java).toSet() } catch (e: Exception) { emptySet() } @@ -310,7 +308,7 @@ class SettingsRepository(private val context: Context) { val json = prefs.getString(KEY_CALENDAR_SYNC_SELECTED_CALENDARS, null) return if (json != null) { try { - gson.fromJson(json, object : TypeToken>() {}.type) ?: emptySet() + gson.fromJson(json, Array::class.java).toSet() } catch (e: Exception) { emptySet() } @@ -332,9 +330,8 @@ class SettingsRepository(private val context: Context) { private fun loadAppSelection(key: String): List { val json = prefs.getString(key, null) return if (json != null) { - val type = object : TypeToken>() {}.type try { - gson.fromJson(json, type) ?: emptyList() + gson.fromJson(json, Array::class.java).toList() } catch (e: Exception) { emptyList() } @@ -413,10 +410,8 @@ class SettingsRepository(private val context: Context) { fun loadSnoozeDiscoveredChannels(): List { val json = prefs.getString(KEY_SNOOZE_DISCOVERED_CHANNELS, null) return if (json != null) { - val type = object : - TypeToken>() {}.type try { - gson.fromJson(json, type) ?: emptyList() + gson.fromJson(json, Array::class.java).toList() } catch (e: Exception) { emptyList() } @@ -433,9 +428,8 @@ class SettingsRepository(private val context: Context) { fun loadSnoozeBlockedChannels(): Set { val json = prefs.getString(KEY_SNOOZE_BLOCKED_CHANNELS, null) return if (json != null) { - val type = object : TypeToken>() {}.type try { - gson.fromJson(json, type) ?: emptySet() + gson.fromJson(json, Array::class.java).toSet() } catch (e: Exception) { emptySet() } @@ -453,10 +447,8 @@ class SettingsRepository(private val context: Context) { fun loadMapsDiscoveredChannels(): List { val json = prefs.getString(KEY_MAPS_DISCOVERED_CHANNELS, null) return if (json != null) { - val type = object : - TypeToken>() {}.type try { - gson.fromJson(json, type) ?: emptyList() + gson.fromJson(json, Array::class.java).toList() } catch (e: Exception) { emptyList() } @@ -473,9 +465,8 @@ class SettingsRepository(private val context: Context) { fun loadMapsDetectionChannels(): Set { val json = prefs.getString(KEY_MAPS_DETECTION_CHANNELS, null) return if (json != null) { - val type = object : TypeToken>() {}.type try { - gson.fromJson(json, type) ?: emptySet() + gson.fromJson(json, Array::class.java).toSet() } catch (e: Exception) { emptySet() } @@ -510,9 +501,8 @@ class SettingsRepository(private val context: Context) { val wrapperMap = mutableMapOf>() p.all.forEach { (key, value) -> - // Skip app lists as requested, and stale data - if (key.endsWith("_selected_apps") || key == "freeze_auto_excluded_apps" || - key.startsWith("mac_battery_") || key == "airsync_mac_connected" || + if (key == "freeze_auto_excluded_apps" || key.endsWith("_selected_apps")) { + } else if (key.startsWith("mac_battery_") || key == "airsync_mac_connected" || key == KEY_SNOOZE_DISCOVERED_CHANNELS || key == KEY_MAPS_DISCOVERED_CHANNELS ) { return@forEach @@ -554,8 +544,7 @@ class SettingsRepository(private val context: Context) { fun importConfigs(inputStream: java.io.InputStream): Boolean { return try { val json = inputStream.bufferedReader().use { it.readText() } - val type = object : TypeToken>>>() {}.type - val allConfigs: Map>> = gson.fromJson(json, type) + val allConfigs: Map>> = gson.fromJson(json, Map::class.java) as Map>> allConfigs.forEach { (fileName, prefWrapper) -> val p = context.getSharedPreferences(fileName, Context.MODE_PRIVATE) @@ -599,10 +588,8 @@ class SettingsRepository(private val context: Context) { fun getBluetoothDevicesBattery(): List { val json = prefs.getString(KEY_BLUETOOTH_DEVICES_BATTERY, null) ?: return emptyList() - val type = object : - TypeToken>() {}.type return try { - gson.fromJson(json, type) ?: emptyList() + gson.fromJson(json, Array::class.java).toList() } catch (e: Exception) { emptyList() } @@ -630,7 +617,7 @@ class SettingsRepository(private val context: Context) { val json = prefs.getString(KEY_PINNED_FEATURES, null) return if (json != null) { try { - gson.fromJson(json, object : TypeToken>() {}.type) ?: emptyList() + gson.fromJson(json, Array::class.java).toList() } catch (e: Exception) { emptyList() } @@ -644,9 +631,8 @@ class SettingsRepository(private val context: Context) { fun getTrackedRepos(): List { val json = prefs.getString(KEY_TRACKED_REPOS, null) ?: return emptyList() - val type = object : TypeToken>() {}.type return try { - gson.fromJson(json, type) + gson.fromJson(json, Array::class.java).toList() } catch (e: Exception) { emptyList() } diff --git a/app/src/main/java/com/sameerasw/essentials/data/repository/UpdateRepository.kt b/app/src/main/java/com/sameerasw/essentials/data/repository/UpdateRepository.kt index 71d14030c..0ebb528af 100644 --- a/app/src/main/java/com/sameerasw/essentials/data/repository/UpdateRepository.kt +++ b/app/src/main/java/com/sameerasw/essentials/data/repository/UpdateRepository.kt @@ -1,7 +1,6 @@ package com.sameerasw.essentials.data.repository import com.google.gson.Gson -import com.google.gson.reflect.TypeToken import com.sameerasw.essentials.domain.model.UpdateInfo import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -30,8 +29,8 @@ class UpdateRepository { val releaseData = connection.inputStream.bufferedReader().readText() val release: Map? = if (isPreReleaseCheckEnabled) { - val listType = object : TypeToken>>() {}.type - val releases: List> = Gson().fromJson(releaseData, listType) + val releases = Gson().fromJson(releaseData, Array::class.java) + .filterIsInstance>() // Find the true latest release using SemanticVersion comparison releases.maxByOrNull { rel -> @@ -39,8 +38,7 @@ class UpdateRepository { SemanticVersion.parse(tagName) } } else { - val mapType = object : TypeToken>() {}.type - Gson().fromJson(releaseData, mapType) + Gson().fromJson(releaseData, Map::class.java) as? Map } if (release == null) return@withContext null diff --git a/app/src/main/java/com/sameerasw/essentials/domain/DIYTabs.kt b/app/src/main/java/com/sameerasw/essentials/domain/DIYTabs.kt index 10303124d..5e2396378 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/DIYTabs.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/DIYTabs.kt @@ -4,7 +4,7 @@ import androidx.annotation.StringRes import com.sameerasw.essentials.R enum class DIYTabs(@StringRes val title: Int, val subtitle: Any, val iconRes: Int) { - ESSENTIALS(R.string.tab_essentials, "(◍•ᴗ•◍)", R.drawable.ic_stat_name), + ESSENTIALS(R.string.tab_essentials, if (com.sameerasw.essentials.BuildConfig.DEBUG) "ʕ •ᴥ• ʔ Debug" else "(◍•ᴗ•◍)", R.drawable.ic_stat_name), FREEZE(R.string.tab_freeze, R.string.tab_freeze_subtitle, R.drawable.rounded_mode_cool_24), DIY(R.string.tab_diy, R.string.tab_diy_subtitle, R.drawable.rounded_experiment_24), APPS(R.string.tab_apps, R.string.tab_apps_subtitle, R.drawable.rounded_apps_24) diff --git a/app/src/main/java/com/sameerasw/essentials/domain/diy/DIYRepository.kt b/app/src/main/java/com/sameerasw/essentials/domain/diy/DIYRepository.kt index f88c32bb0..2eeffcce9 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/diy/DIYRepository.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/diy/DIYRepository.kt @@ -65,14 +65,14 @@ object DIYRepository { fun init(context: Context) { if (prefs != null) return prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - loadAutomations() + reloadAutomations() } - private fun loadAutomations() { + fun reloadAutomations() { val json = prefs?.getString(KEY_AUTOMATIONS, null) val loadedList: List = if (json != null) { try { - val type = object : TypeToken>() {}.type + val type = object : com.google.gson.reflect.TypeToken>() {}.type gson.fromJson(json, type) ?: emptyList() } catch (e: Exception) { emptyList() diff --git a/app/src/main/java/com/sameerasw/essentials/services/BatteryNotificationService.kt b/app/src/main/java/com/sameerasw/essentials/services/BatteryNotificationService.kt index 24b2ca6a0..50d5a43e4 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/BatteryNotificationService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/BatteryNotificationService.kt @@ -17,7 +17,6 @@ import com.sameerasw.essentials.data.repository.SettingsRepository import com.sameerasw.essentials.utils.BatteryRingDrawer import com.sameerasw.essentials.utils.BluetoothBatteryUtils import com.google.gson.Gson -import com.google.gson.reflect.TypeToken class BatteryNotificationService : Service() { @@ -141,8 +140,7 @@ class BatteryNotificationService : Service() { if (isShowBluetoothEnabled && !bluetoothJson.isNullOrEmpty() && bluetoothJson != "[]") { try { - val type = object : TypeToken>() {}.type - val devices: List = Gson().fromJson(bluetoothJson, type) ?: emptyList() + val devices: List = Gson().fromJson(bluetoothJson, Array::class.java).toList() devices.forEach { device -> val iconRes = when { device.name.contains("watch", true) || device.name.contains("gear", true) || device.name.contains("fit", true) -> R.drawable.rounded_watch_24 diff --git a/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt b/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt index 8855cb114..26d99b8ff 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt @@ -77,12 +77,10 @@ class NotificationListener : NotificationListenerService() { ) val discoveredJson = prefs.getString("maps_discovered_channels", null) val gson = com.google.gson.Gson() - val type = object : - com.google.gson.reflect.TypeToken>() {}.type val discoveredChannels: MutableList = if (discoveredJson != null) { try { - gson.fromJson(discoveredJson, type) ?: mutableListOf() + gson.fromJson(discoveredJson, Array::class.java).toMutableList() } catch (_: Exception) { mutableListOf() } @@ -131,12 +129,10 @@ class NotificationListener : NotificationListenerService() { ) val discoveredJson = prefs.getString("snooze_discovered_channels", null) val gson = com.google.gson.Gson() - val type = object : - com.google.gson.reflect.TypeToken>() {}.type val discoveredChannels: MutableList = if (discoveredJson != null) { try { - gson.fromJson(discoveredJson, type) ?: mutableListOf() + gson.fromJson(discoveredJson, Array::class.java).toMutableList() } catch (_: Exception) { mutableListOf() } @@ -566,9 +562,7 @@ class NotificationListener : NotificationListenerService() { val blockedChannelsJson = prefs.getString("snooze_blocked_channels", null) val blockedChannels: Set = if (blockedChannelsJson != null) { try { - val type = - object : com.google.gson.reflect.TypeToken>() {}.type - com.google.gson.Gson().fromJson(blockedChannelsJson, type) ?: emptySet() + com.google.gson.Gson().fromJson(blockedChannelsJson, Array::class.java).toSet() } catch (_: Exception) { emptySet() } @@ -665,10 +659,8 @@ class NotificationListener : NotificationListenerService() { val gson = com.google.gson.Gson() val glowSidesJson = prefs.getString("edge_lighting_glow_sides", null) val glowSides: Set = if (glowSidesJson != null) { - val type = object : - com.google.gson.reflect.TypeToken>() {}.type try { - gson.fromJson(glowSidesJson, type) + gson.fromJson(glowSidesJson, Array::class.java).toSet() } catch (_: Exception) { setOf(NotificationLightingSide.LEFT, NotificationLightingSide.RIGHT) } @@ -856,8 +848,7 @@ class NotificationListener : NotificationListenerService() { val detectionChannelsJson = prefs.getString("maps_detection_channels", null) val detectionChannels: Set = if (detectionChannelsJson != null) { try { - val type = object : com.google.gson.reflect.TypeToken>() {}.type - com.google.gson.Gson().fromJson(detectionChannelsJson, type) ?: emptySet() + com.google.gson.Gson().fromJson(detectionChannelsJson, Array::class.java).toSet() } catch (_: Exception) { emptySet() } @@ -917,10 +908,8 @@ class NotificationListener : NotificationListenerService() { // If no saved preferences, allow all apps by default val gson = com.google.gson.Gson() - val type = object : - com.google.gson.reflect.TypeToken>() {}.type val selectedApps: List = - gson.fromJson(json, type) + gson.fromJson(json, Array::class.java).toList() // Find the app in the saved list val app = selectedApps.find { it.packageName == packageName } @@ -951,10 +940,8 @@ class NotificationListener : NotificationListenerService() { } val gson = com.google.gson.Gson() - val type = object : - com.google.gson.reflect.TypeToken>() {}.type val selectedApps: List = - gson.fromJson(json, type) + gson.fromJson(json, Array::class.java).toList() // Find the app in the saved list val app = selectedApps.find { it.packageName == packageName } diff --git a/app/src/main/java/com/sameerasw/essentials/services/handlers/AppFlowHandler.kt b/app/src/main/java/com/sameerasw/essentials/services/handlers/AppFlowHandler.kt index 42c96b81b..20d3f215a 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/handlers/AppFlowHandler.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/handlers/AppFlowHandler.kt @@ -9,7 +9,6 @@ import android.os.Looper import android.provider.Settings import android.util.Log import com.google.gson.Gson -import com.google.gson.reflect.TypeToken import com.sameerasw.essentials.domain.diy.Automation import com.sameerasw.essentials.domain.diy.DIYRepository import com.sameerasw.essentials.domain.model.AppSelection @@ -78,7 +77,7 @@ class AppFlowHandler( val json = prefs.getString("app_lock_selected_apps", null) val selectedApps: List = if (json != null) { try { - Gson().fromJson(json, object : TypeToken>() {}.type) + Gson().fromJson(json, Array::class.java).toList() } catch (_: Exception) { emptyList() } @@ -134,7 +133,7 @@ class AppFlowHandler( val json = prefs.getString("dynamic_night_light_selected_apps", null) val selectedApps: List = if (json != null) { try { - Gson().fromJson(json, object : TypeToken>() {}.type) + Gson().fromJson(json, Array::class.java).toList() } catch (_: Exception) { emptyList() } diff --git a/app/src/main/java/com/sameerasw/essentials/services/receivers/AirSyncBridgeReceiver.kt b/app/src/main/java/com/sameerasw/essentials/services/receivers/AirSyncBridgeReceiver.kt index 22dd39fe9..02a5ff850 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/receivers/AirSyncBridgeReceiver.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/receivers/AirSyncBridgeReceiver.kt @@ -21,7 +21,14 @@ class AirSyncBridgeReceiver : BroadcastReceiver() { ) val repository = SettingsRepository(context) - if (repository.getBoolean(SettingsRepository.KEY_AIRSYNC_CONNECTION_ENABLED)) { + val isEnabled = repository.getBoolean(SettingsRepository.KEY_AIRSYNC_CONNECTION_ENABLED) + + android.util.Log.d( + "AirSyncBridge", + "Received Mac status broadcast. Bridge enabled: $isEnabled, level=$level, charging=$isCharging" + ) + + if (isEnabled) { repository.putInt(SettingsRepository.KEY_MAC_BATTERY_LEVEL, level) repository.putBoolean(SettingsRepository.KEY_MAC_BATTERY_IS_CHARGING, isCharging) repository.putLong(SettingsRepository.KEY_MAC_BATTERY_LAST_UPDATED, lastUpdated) diff --git a/app/src/main/java/com/sameerasw/essentials/services/widgets/BatteriesWidget.kt b/app/src/main/java/com/sameerasw/essentials/services/widgets/BatteriesWidget.kt index f1fe4017a..9c262519a 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/widgets/BatteriesWidget.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/widgets/BatteriesWidget.kt @@ -126,10 +126,8 @@ class BatteriesWidget : GlanceAppWidget() { // Bluetooth Items if (hasBluetooth) { try { - val type = object : - com.google.gson.reflect.TypeToken>() {}.type val devices: List = - com.google.gson.Gson().fromJson(bluetoothJson, type) ?: emptyList() + com.google.gson.Gson().fromJson(bluetoothJson, Array::class.java).toList() devices.forEach { device -> val iconRes = when { diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FreezeSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FreezeSettingsUI.kt index 0e9dad452..ae5357e05 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FreezeSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FreezeSettingsUI.kt @@ -1,5 +1,7 @@ package com.sameerasw.essentials.ui.composables.configs +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -69,6 +71,34 @@ fun FreezeSettingsUI( var isMenuExpanded by remember { mutableStateOf(false) } + val exportLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.CreateDocument("application/json") + ) { uri -> + uri?.let { + try { + context.contentResolver.openOutputStream(it)?.use { stream -> + viewModel.exportFreezeApps(stream) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + val importLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.OpenDocument() + ) { uri -> + uri?.let { + try { + context.contentResolver.openInputStream(it)?.use { stream -> + viewModel.importFreezeApps(context, stream) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + remember { MutableInteractionSource() } remember { MutableInteractionSource() } remember { MutableInteractionSource() } @@ -199,6 +229,36 @@ fun FreezeSettingsUI( ) } ) + DropdownMenuItem( + text = { Text(stringResource(R.string.action_export_freeze)) }, + onClick = { + HapticUtil.performVirtualKeyHaptic(view) + exportLauncher.launch("freeze_apps_backup.json") + isMenuExpanded = false + }, + leadingIcon = { + Icon( + painter = painterResource(id = R.drawable.rounded_arrow_warm_up_24), + contentDescription = null, + modifier = Modifier.size(18.dp) + ) + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.action_import_freeze)) }, + onClick = { + HapticUtil.performVirtualKeyHaptic(view) + importLauncher.launch(arrayOf("application/json")) + isMenuExpanded = false + }, + leadingIcon = { + Icon( + painter = painterResource(id = R.drawable.rounded_arrow_cool_down_24), + contentDescription = null, + modifier = Modifier.size(18.dp) + ) + } + ) } } } diff --git a/app/src/main/java/com/sameerasw/essentials/utils/FreezeManager.kt b/app/src/main/java/com/sameerasw/essentials/utils/FreezeManager.kt index 5bd3069ad..5c48d0262 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/FreezeManager.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/FreezeManager.kt @@ -43,15 +43,11 @@ object FreezeManager { if (json != null) { val gson = com.google.gson.Gson() - val type = object : - com.google.gson.reflect.TypeToken>() {}.type - val excludedType = object : com.google.gson.reflect.TypeToken>() {}.type - try { val apps: List = - gson.fromJson(json, type) + gson.fromJson(json, Array::class.java).toList() val excludedSet: Set = if (excludedJson != null) { - gson.fromJson(excludedJson, excludedType) ?: emptySet() + gson.fromJson(excludedJson, Array::class.java).toSet() } else emptySet() apps.forEach { app -> @@ -146,11 +142,9 @@ object FreezeManager { val json = prefs.getString("freeze_selected_apps", null) if (json != null) { val gson = com.google.gson.Gson() - val type = object : - com.google.gson.reflect.TypeToken>() {}.type try { val apps: List = - gson.fromJson(json, type) + gson.fromJson(json, Array::class.java).toList() apps.forEach { app -> freezeApp(context, app.packageName) } @@ -170,15 +164,11 @@ object FreezeManager { if (json != null) { val gson = com.google.gson.Gson() - val type = object : - com.google.gson.reflect.TypeToken>() {}.type - val excludedType = object : com.google.gson.reflect.TypeToken>() {}.type - try { val apps: List = - gson.fromJson(json, type) + gson.fromJson(json, Array::class.java).toList() val excludedSet: Set = if (excludedJson != null) { - gson.fromJson(excludedJson, excludedType) ?: emptySet() + gson.fromJson(excludedJson, Array::class.java).toSet() } else emptySet() apps.forEach { app -> @@ -200,11 +190,9 @@ object FreezeManager { val json = prefs.getString("freeze_selected_apps", null) if (json != null) { val gson = com.google.gson.Gson() - val type = object : - com.google.gson.reflect.TypeToken>() {}.type try { val apps: List = - gson.fromJson(json, type) + gson.fromJson(json, Array::class.java).toList() apps.forEach { app -> unfreezeApp(context, app.packageName) } diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/AppUpdatesViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/AppUpdatesViewModel.kt index 12ae2bbac..3b4f2cabe 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/AppUpdatesViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/AppUpdatesViewModel.kt @@ -25,7 +25,6 @@ import java.io.OutputStream import java.net.HttpURLConnection import java.net.URL import com.google.gson.Gson -import com.google.gson.reflect.TypeToken class AppUpdatesViewModel : ViewModel() { private val gitHubRepository = GitHubRepository() @@ -533,8 +532,7 @@ class AppUpdatesViewModel : ViewModel() { fun importTrackedRepos(context: Context, inputStream: InputStream): Boolean { return try { val json = inputStream.bufferedReader().use { it.readText() } - val type = object : TypeToken>() {}.type - val importedRepos: List = gson.fromJson(json, type) + val importedRepos: List = gson.fromJson(json, Array::class.java).toList() if (importedRepos.isNotEmpty()) { val settingsRepo = SettingsRepository(context) val currentRepos = settingsRepo.getTrackedRepos().toMutableList() diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt index 709077df5..fee8c7336 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -2079,11 +2079,60 @@ class MainViewModel : ViewModel() { val success = settingsRepository.importConfigs(inputStream) if (success) { settingsRepository.syncSystemSettingsWithSaved() + com.sameerasw.essentials.domain.diy.DIYRepository.reloadAutomations() + refreshFreezePickedApps(context, silent = true) check(context) } return success } + fun exportFreezeApps(outputStream: java.io.OutputStream) { + try { + val apps = settingsRepository.loadFreezeSelectedApps() + val gson = com.google.gson.Gson() + val json = gson.toJson(apps) + outputStream.write(json.toByteArray()) + outputStream.flush() + } catch (e: Exception) { + e.printStackTrace() + } finally { + try { + outputStream.close() + } catch (e: Exception) { + } + } + } + + fun importFreezeApps(context: Context, inputStream: java.io.InputStream): Boolean { + return try { + val json = inputStream.bufferedReader().use { it.readText() } + val gson = com.google.gson.Gson() + val apps = gson.fromJson(json, Array::class.java).toList() + + // Filter out non-installed apps + val pm = context.packageManager + val installedApps = apps.filter { app -> + try { + pm.getPackageInfo(app.packageName, 0) + true + } catch (e: Exception) { + false + } + } + + settingsRepository.saveFreezeSelectedApps(installedApps) + refreshFreezePickedApps(context, silent = true) + true + } catch (e: Exception) { + e.printStackTrace() + false + } finally { + try { + inputStream.close() + } catch (e: Exception) { + } + } + } fun generateBugReport(context: Context): String { val settingsJson = settingsRepository.getAllConfigsAsJsonString() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 94708047e..217e34bd6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -117,6 +117,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation diff --git a/gradle.properties b/gradle.properties index 0d4b7ed35..68a7494d8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,11 +6,14 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -Dkotlin.daemon.jvm.options="-Xmx2048m" # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. For more details, visit # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects -# org.gradle.parallel=true +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=true +org.gradle.configuration-cache.problems=warn # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn diff --git a/grant_perms.sh b/grant_perms.sh new file mode 100755 index 000000000..4cfbb5d42 --- /dev/null +++ b/grant_perms.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +# Configuration +RELEASE_PKG="com.sameerasw.essentials" +DEBUG_PKG="com.sameerasw.essentials.debug" + +# Function to grant permissions to a specific package +grant_permissions() { + local PKG=$1 + + # Check if package is installed + if ! adb shell pm list packages | grep -q "$PKG"; then + echo "Skipping $PKG (not installed)" + return + fi + + echo "Granting permissions for $PKG..." + + # Runtime Permissions + adb shell pm grant $PKG android.permission.READ_CALENDAR + adb shell pm grant $PKG android.permission.READ_PHONE_STATE + adb shell pm grant $PKG android.permission.POST_NOTIFICATIONS + adb shell pm grant $PKG android.permission.ACCESS_COARSE_LOCATION + adb shell pm grant $PKG android.permission.ACCESS_FINE_LOCATION + adb shell pm grant $PKG android.permission.ACCESS_BACKGROUND_LOCATION + adb shell pm grant $PKG android.permission.BLUETOOTH_CONNECT + adb shell pm grant $PKG android.permission.BLUETOOTH_SCAN + + # Secure Settings + adb shell pm grant $PKG android.permission.WRITE_SECURE_SETTINGS + + # DND Access (Notification Policy) + adb shell cmd notification allow_dnd $PKG + + # AppOps (Special Permissions) + adb shell appops set $PKG GET_USAGE_STATS allow + adb shell appops set $PKG SYSTEM_ALERT_WINDOW allow + adb shell appops set $PKG WRITE_SETTINGS allow + adb shell appops set $PKG REQUEST_INSTALL_PACKAGES allow + adb shell appops set $PKG ACCESS_NOTIFICATIONS allow + + # Enable Services + # Notification Listener + LISTENER="$PKG/com.sameerasw.essentials.services.NotificationListener" + adb shell settings put secure enabled_notification_listeners $LISTENER + + # Accessibility Service + ACCESSIBILITY="$PKG/com.sameerasw.essentials.services.tiles.ScreenOffAccessibilityService" + adb shell settings put secure enabled_accessibility_services $ACCESSIBILITY + adb shell settings put secure accessibility_enabled 1 + + echo "Finished $PKG" +} + +# Run for both +grant_permissions "$RELEASE_PKG" +grant_permissions "$DEBUG_PKG" + +echo "All tasks complete! You might need to restart the apps for some changes to take effect."