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: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,14 @@
</intent-filter>
</activity>

<activity
android:name=".ShutUpShortcutActivity"
android:exported="true"
android:excludeFromRecents="true"
android:noHistory="true"
android:taskAffinity=""
android:theme="@style/Theme.Essentials.Translucent" />

<activity
android:name=".ui.activities.FlashlightIntensityActivity"
android:exported="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import com.sameerasw.essentials.ui.composables.configs.LocationReachedSettingsUI
import com.sameerasw.essentials.ui.composables.configs.MapsPowerSavingSettingsUI
import com.sameerasw.essentials.ui.composables.configs.NotificationLightingSettingsUI
import com.sameerasw.essentials.ui.composables.configs.OtherCustomizationsSettingsUI
import com.sameerasw.essentials.ui.composables.configs.ShutUpSettingsUI
import com.sameerasw.essentials.ui.composables.configs.QuickSettingsTilesSettingsUI
import com.sameerasw.essentials.ui.composables.configs.RefreshRateSettingsUI
import com.sameerasw.essentials.ui.composables.configs.ScreenLockedSecuritySettingsUI
Expand Down Expand Up @@ -270,6 +271,7 @@ class FeatureSettingsActivity : AppCompatActivity() {
"Text and animations" -> !viewModel.isWriteSettingsEnabled.value || !isWriteSecureSettingsEnabled
"Always on Display" -> !isWriteSecureSettingsEnabled
"Other customizations" -> !com.sameerasw.essentials.utils.ShellUtils.hasPermission(context)
"Shut-Up!" -> !isWriteSecureSettingsEnabled || !viewModel.isUsageStatsPermissionGranted.value
else -> false
}
if (hasMissingPermissions) {
Expand Down Expand Up @@ -412,6 +414,7 @@ class FeatureSettingsActivity : AppCompatActivity() {
"Battery notification" -> !viewModel.isPostNotificationsEnabled.value || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !viewModel.isBluetoothPermissionGranted.value)
"Text and animations" -> !viewModel.isWriteSettingsEnabled.value || !isWriteSecureSettingsEnabled
"Screen refresh rate" -> !viewModel.isShizukuPermissionGranted.value
"Shut-Up!" -> !isWriteSecureSettingsEnabled || !viewModel.isUsageStatsPermissionGranted.value
else -> false
}

Expand Down Expand Up @@ -678,6 +681,14 @@ class FeatureSettingsActivity : AppCompatActivity() {
highlightSetting = highlightSetting
)
}

"Shut-Up!" -> {
ShutUpSettingsUI(
viewModel = viewModel,
modifier = Modifier.padding(top = 16.dp),
highlightKey = highlightSetting
)
}
}

}
Expand Down
168 changes: 168 additions & 0 deletions app/src/main/java/com/sameerasw/essentials/ShutUpShortcutActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package com.sameerasw.essentials

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.LoadingIndicator
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.lifecycle.lifecycleScope
import com.sameerasw.essentials.data.repository.SettingsRepository
import com.sameerasw.essentials.domain.model.ShutUpAppConfig
import com.sameerasw.essentials.ui.theme.EssentialsTheme
import com.sameerasw.essentials.utils.PermissionUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class ShutUpShortcutActivity : ComponentActivity() {

@OptIn(ExperimentalMaterial3ExpressiveApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()

val packageName = intent.getStringExtra("package_name")
if (packageName == null) {
finish()
return
}

setContent {
val viewModel: com.sameerasw.essentials.viewmodels.MainViewModel =
androidx.lifecycle.viewmodel.compose.viewModel()
val context = androidx.compose.ui.platform.LocalContext.current
androidx.compose.runtime.LaunchedEffect(Unit) {
viewModel.check(context)
}
val isPitchBlackThemeEnabled by viewModel.isPitchBlackThemeEnabled
EssentialsTheme(pitchBlackTheme = isPitchBlackThemeEnabled) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
LoadingIndicator(modifier = Modifier.scale(5f))
}
}
}

val settingsRepository = SettingsRepository(this)
val config = settingsRepository.loadShutUpConfigs().find { it.packageName == packageName }

lifecycleScope.launch {
// Unfreeze first while Shizuku/Root is still functional
if (com.sameerasw.essentials.utils.FreezeManager.isAppFrozen(this@ShutUpShortcutActivity, packageName)) {
com.sameerasw.essentials.utils.FreezeManager.unfreezeApp(this@ShutUpShortcutActivity, packageName)
delay(200) // Small extra delay for system to register unfreeze
}

if (config != null && config.isEnabled) {
if (PermissionUtils.canWriteSecureSettings(this@ShutUpShortcutActivity)) {
applyShutUpSettings(config, settingsRepository)
withContext(Dispatchers.Main) {
Toast.makeText(this@ShutUpShortcutActivity, getString(R.string.shut_up_toast_active), Toast.LENGTH_SHORT).show()
}
}
}

// Delay to ensure system registers the settings changes
delay(800)

launchApp(packageName)
finish()
}
}

private suspend fun applyShutUpSettings(config: ShutUpAppConfig, repository: SettingsRepository) {
withContext(Dispatchers.IO) {
val originalSettings = mutableMapOf<String, String>()

if (config.disableDevOptions) {
// Backup all relevant dev settings because disabling the main toggle might reset them
val secureSettings = listOf(
"anr_show_background", "bugreport_in_power_menu", "display_density_forced",
"mock_location", "secure_overlay_settings", "usb_audio_automatic_routing_disabled"
)
val systemSettings = listOf("show_touches", "show_key_presses")
val globalSettings = listOf(
"adb_allowed_connection_time", "adb_enabled", "adb_wifi_enabled",
"always_finish_activities", "animator_duration_scale", "app_standby_enabled",
"cached_apps_freezer", "default_install_location", "development_settings_enabled",
"disable_window_blurs", "enable_freeform_support", "enable_non_resizable_multi_window",
"force_allow_on_external", "force_desktop_mode_on_external_displays", "force_resizable_activities",
"mobile_data_always_on", "stay_on_while_plugged_in", "usb_mass_storage_enabled",
"wait_for_debugger", "wifi_display_certification_on", "wifi_display_on",
"wifi_scan_always_enabled", "window_animation_scale"
)

secureSettings.forEach { key ->
Settings.Secure.getString(contentResolver, key)?.let { originalSettings["secure:$key"] = it }
}
systemSettings.forEach { key ->
Settings.System.getString(contentResolver, key)?.let { originalSettings["system:$key"] = it }
}
globalSettings.forEach { key ->
Settings.Global.getString(contentResolver, key)?.let { originalSettings["global:$key"] = it }
}

// Disable dev options
Settings.Global.putString(contentResolver, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, "0")
}

// Always explicitly disable USB debugging if requested, even if dev options were already disabled
// as some apps check this specific setting directly.
if (config.disableUsbDebugging) {
val current = Settings.Global.getString(contentResolver, Settings.Global.ADB_ENABLED) ?: "0"
if (current == "1") {
if (!originalSettings.containsKey("global:${Settings.Global.ADB_ENABLED}")) {
originalSettings["global:${Settings.Global.ADB_ENABLED}"] = "1"
}
Settings.Global.putString(contentResolver, Settings.Global.ADB_ENABLED, "0")
}
}

if (config.disableWirelessDebugging) {
val current = Settings.Global.getString(contentResolver, "adb_wifi_enabled") ?: "0"
if (current == "1") {
if (!originalSettings.containsKey("global:adb_wifi_enabled")) {
originalSettings["global:adb_wifi_enabled"] = "1"
}
Settings.Global.putString(contentResolver, "adb_wifi_enabled", "0")
}
}

if (config.disableAccessibility) {
val current = Settings.Secure.getString(contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
if (!current.isNullOrEmpty()) {
originalSettings["secure:${Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES}"] = current
Settings.Secure.putString(contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "")
}
}

if (originalSettings.isNotEmpty()) {
repository.saveShutUpOriginalSettings(originalSettings)
}
}
}

private fun launchApp(packageName: String) {
val intent = packageManager.getLaunchIntentForPackage(packageName)
if (intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
} else {
Toast.makeText(this, "Could not launch $packageName", Toast.LENGTH_SHORT).show()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ class SettingsRepository(private val context: Context) {
const val LIVE_WALLPAPER_DEFAULT_VIDEO = "my_video"
const val LIVE_WALLPAPER_TRIGGER_UNLOCK = "unlock"
const val LIVE_WALLPAPER_TRIGGER_SCREEN_ON = "screen_on"

const val KEY_SHUT_UP_SELECTED_APPS = "shut_up_selected_apps"
const val KEY_SHUT_UP_ORIGINAL_SETTINGS = "shut_up_original_settings"
}

// Observe changes
Expand Down Expand Up @@ -480,6 +483,50 @@ class SettingsRepository(private val context: Context) {
fun updateNotificationGlanceAppSelection(packageName: String, enabled: Boolean) =
updateAppSelection(KEY_NOTIFICATION_GLANCE_SELECTED_APPS, packageName, enabled)

fun loadShutUpConfigs(): List<com.sameerasw.essentials.domain.model.ShutUpAppConfig> {
val json = prefs.getString(KEY_SHUT_UP_SELECTED_APPS, null)
return if (json != null) {
try {
gson.fromJson(json, Array<com.sameerasw.essentials.domain.model.ShutUpAppConfig>::class.java).toList()
} catch (e: Exception) {
emptyList()
}
} else {
emptyList()
}
}

fun saveShutUpConfigs(configs: List<com.sameerasw.essentials.domain.model.ShutUpAppConfig>) {
val json = gson.toJson(configs)
putString(KEY_SHUT_UP_SELECTED_APPS, json)
}

fun updateShutUpConfig(config: com.sameerasw.essentials.domain.model.ShutUpAppConfig) {
val current = loadShutUpConfigs().toMutableList()
val index = current.indexOfFirst { it.packageName == config.packageName }
if (index != -1) {
current[index] = config
} else {
current.add(config)
}
saveShutUpConfigs(current)
}

fun saveShutUpOriginalSettings(settings: Map<String, String>) {
val json = gson.toJson(settings)
putString(KEY_SHUT_UP_ORIGINAL_SETTINGS, json)
}

fun getShutUpOriginalSettings(): Map<String, String> {
val json = prefs.getString(KEY_SHUT_UP_ORIGINAL_SETTINGS, null) ?: return emptyMap()
return try {
@Suppress("UNCHECKED_CAST")
gson.fromJson(json, Map::class.java) as Map<String, String>
} catch (e: Exception) {
emptyMap()
}
}

private fun updateAppSelection(key: String, packageName: String, enabled: Boolean) {
val current = loadAppSelection(key).toMutableList()
val index = current.indexOfFirst { it.packageName == packageName }
Expand Down Expand Up @@ -601,7 +648,8 @@ class SettingsRepository(private val context: Context) {
p.all.forEach { (key, value) ->
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
key == KEY_SNOOZE_DISCOVERED_CHANNELS || key == KEY_MAPS_DISCOVERED_CHANNELS ||
key == KEY_SHUT_UP_ORIGINAL_SETTINGS
) {
return@forEach
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.sameerasw.essentials.domain.model

data class ShutUpAppConfig(
val packageName: String,
val isEnabled: Boolean = true,
val disableDevOptions: Boolean = true,
val disableUsbDebugging: Boolean = true,
val disableWirelessDebugging: Boolean = true,
val disableAccessibility: Boolean = false,
val autoArchive: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,21 @@ object FeatureRegistry {
override fun isEnabled(viewModel: MainViewModel) = true
override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
},
object : Feature(
id = "Shut-Up!",
title = R.string.feat_shut_up_title,
iconRes = R.drawable.rounded_domino_mask_24,
category = R.string.cat_system,
description = R.string.feat_shut_up_desc,
aboutDescription = R.string.shut_up_description,
permissionKeys = listOf("WRITE_SECURE_SETTINGS", "USAGE_STATS"),
showToggle = false,
hasMoreSettings = true,
parentFeatureId = "Security"
) {
override fun isEnabled(viewModel: MainViewModel) = true
override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
},
object : Feature(
id = "Notifications",
title = R.string.feat_notifications_alerts_title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,9 @@ fun initPermissionRegistry() {

// Default browser permission
PermissionRegistry.register("DEFAULT_BROWSER", R.string.feat_link_actions_title)

// Shut-Up! feature
PermissionRegistry.register("WRITE_SECURE_SETTINGS", R.string.feat_shut_up_title)
PermissionRegistry.register("WRITE_SETTINGS", R.string.feat_shut_up_title)
PermissionRegistry.register("USAGE_STATS", R.string.feat_shut_up_title)
}
Loading