Skip to content

Commit f7a0884

Browse files
authored
Merge pull request #7 from sameerasw/develop
Develop
2 parents 1515c76 + 3c454d6 commit f7a0884

15 files changed

+547
-77
lines changed

app/build.gradle.kts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ android {
1414
applicationId = "com.sameerasw.essentials"
1515
minSdk = 23
1616
targetSdk = 36
17-
versionCode = 4
18-
versionName = "3.1"
17+
versionCode = 5
18+
versionName = "4.0"
1919

2020
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2121
}
@@ -68,4 +68,7 @@ dependencies {
6868

6969
// Gson for JSON serialization
7070
implementation("com.google.code.gson:gson:2.10.1")
71+
72+
// Reorderable library
73+
implementation("sh.calvin.reorderable:reorderable:3.0.0")
7174
}

app/src/main/AndroidManifest.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,16 @@
9696
android:name=".services.CaffeinateWakeLockService"
9797
android:exported="false"
9898
android:foregroundServiceType="specialUse" />
99+
<service
100+
android:name=".services.SoundModeTileService"
101+
android:exported="true"
102+
android:icon="@drawable/rounded_volume_up_24"
103+
android:label="Sound Mode"
104+
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
105+
<intent-filter>
106+
<action android:name="android.service.quicksettings.action.QS_TILE" />
107+
</intent-filter>
108+
</service>
99109
<service
100110
android:name=".services.EdgeLightingService"
101111
android:exported="false"

app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import com.sameerasw.essentials.ui.composables.configs.StatusBarIconSettingsUI
3838
import com.sameerasw.essentials.ui.composables.configs.CaffeinateSettingsUI
3939
import com.sameerasw.essentials.ui.composables.configs.ScreenOffWidgetSettingsUI
4040
import com.sameerasw.essentials.ui.composables.configs.EdgeLightingSettingsUI
41+
import com.sameerasw.essentials.ui.composables.configs.SoundModeTileSettingsUI
4142
import com.sameerasw.essentials.viewmodels.CaffeinateViewModel
4243
import com.sameerasw.essentials.viewmodels.MainViewModel
4344
import com.sameerasw.essentials.viewmodels.StatusBarIconViewModel
@@ -55,7 +56,8 @@ class FeatureSettingsActivity : ComponentActivity() {
5556
"Screen off widget" to "Invisible widget to turn the screen off",
5657
"Statusbar icons" to "Control statusbar icons visibility",
5758
"Caffeinate" to "Keep the screen awake",
58-
"Edge lighting" to "Preview edge lighting effects on new notifications"
59+
"Edge lighting" to "Preview edge lighting effects on new notifications",
60+
"Sound mode tile" to "QS tile to toggle sound mode"
5961
)
6062
val description = featureDescriptions[feature] ?: ""
6163
setContent {
@@ -76,12 +78,12 @@ class FeatureSettingsActivity : ComponentActivity() {
7678
}
7779

7880
var selectedHaptic by remember {
79-
val name = prefs.getString("haptic_feedback_type", HapticFeedbackType.SUBTLE.name)
81+
val name = prefs.getString("haptic_feedback_type", HapticFeedbackType.NONE.name)
8082
mutableStateOf(
8183
try {
82-
HapticFeedbackType.valueOf(name ?: HapticFeedbackType.SUBTLE.name)
84+
HapticFeedbackType.valueOf(name ?: HapticFeedbackType.NONE.name)
8385
} catch (@Suppress("UNUSED_PARAMETER") e: Exception) {
84-
HapticFeedbackType.SUBTLE
86+
HapticFeedbackType.NONE
8587
}
8688
)
8789
}
@@ -204,11 +206,12 @@ class FeatureSettingsActivity : ComponentActivity() {
204206
)
205207
}
206208
) { innerPadding ->
209+
val hasScroll = feature != "Sound mode tile"
207210
Column(
208211
modifier = Modifier
209212
.padding(innerPadding)
210213
.fillMaxSize()
211-
.verticalScroll(rememberScrollState())
214+
.then(if (hasScroll) Modifier.verticalScroll(rememberScrollState()) else Modifier)
212215
) {
213216
when (feature) {
214217
"Screen off widget" -> {
@@ -244,6 +247,9 @@ class FeatureSettingsActivity : ComponentActivity() {
244247
"Edge lighting" -> {
245248
EdgeLightingSettingsUI(viewModel = viewModel, modifier = Modifier.padding(top = 16.dp))
246249
}
250+
"Sound mode tile" -> {
251+
SoundModeTileSettingsUI(modifier = Modifier.padding(top = 16.dp))
252+
}
247253
else -> {
248254
ScreenOffWidgetSettingsUI(
249255
viewModel = viewModel,

app/src/main/java/com/sameerasw/essentials/PermissionRegistry.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ fun initPermissionRegistry() {
1717
PermissionRegistry.register("ACCESSIBILITY", "Screen off widget")
1818
// Key for write secure settings
1919
PermissionRegistry.register("WRITE_SECURE_SETTINGS", "Statusbar icons")
20+
PermissionRegistry.register("WRITE_SECURE_SETTINGS", "Sound Mode")
2021
// Key for Shizuku (maps power saving)
2122
PermissionRegistry.register("SHIZUKU", "Maps power saving mode")
2223
// Key for notification listener permission

app/src/main/java/com/sameerasw/essentials/ScreenOffWidgetProvider.kt

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import android.appwidget.AppWidgetManager
55
import android.appwidget.AppWidgetProvider
66
import android.content.Context
77
import android.content.Intent
8+
import android.provider.Settings
89
import android.widget.RemoteViews
10+
import android.widget.Toast
911
import com.sameerasw.essentials.services.ScreenOffAccessibilityService
1012

1113
class ScreenOffWidgetProvider : AppWidgetProvider() {
@@ -16,20 +18,33 @@ class ScreenOffWidgetProvider : AppWidgetProvider() {
1618
}
1719
}
1820

21+
override fun onReceive(context: Context, intent: Intent) {
22+
super.onReceive(context, intent)
23+
if (intent.action == "WIDGET_CLICK") {
24+
if (isAccessibilityEnabled(context)) {
25+
val serviceIntent = Intent(context, ScreenOffAccessibilityService::class.java).apply {
26+
action = "LOCK_SCREEN"
27+
}
28+
context.startService(serviceIntent)
29+
} else {
30+
Toast.makeText(context, "Missing permissions, Check the app", Toast.LENGTH_SHORT).show()
31+
}
32+
}
33+
}
34+
35+
private fun isAccessibilityEnabled(context: Context): Boolean {
36+
val enabledServices = Settings.Secure.getString(context.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
37+
return enabledServices?.contains("com.sameerasw.essentials.services.ScreenOffAccessibilityService") == true
38+
}
39+
1940
private fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
2041
val views = RemoteViews(context.packageName, R.layout.screen_off_widget)
2142

22-
val prefs = context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE)
23-
val enabled = prefs.getBoolean("widget_enabled", false)
24-
25-
if (enabled) {
26-
val intent = Intent(context, ScreenOffAccessibilityService::class.java).apply {
27-
action = "LOCK_SCREEN"
28-
}
29-
val pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
30-
views.setOnClickPendingIntent(R.id.widget_container, pendingIntent)
43+
val intent = Intent(context, ScreenOffWidgetProvider::class.java).apply {
44+
action = "WIDGET_CLICK"
3145
}
32-
// If not enabled, do not set the pending intent, so tapping does nothing
46+
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
47+
views.setOnClickPendingIntent(R.id.widget_container, pendingIntent)
3348

3449
appWidgetManager.updateAppWidget(appWidgetId, views)
3550
}

app/src/main/java/com/sameerasw/essentials/services/BaseTileService.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package com.sameerasw.essentials.services
22

3+
import android.graphics.drawable.Icon
4+
import android.os.Build
35
import android.service.quicksettings.Tile
46
import android.service.quicksettings.TileService
7+
import androidx.annotation.RequiresApi
58

9+
@RequiresApi(Build.VERSION_CODES.N)
610
abstract class BaseTileService : TileService() {
711

812
abstract fun onTileClick()
@@ -13,6 +17,8 @@ abstract class BaseTileService : TileService() {
1317

1418
abstract fun hasFeaturePermission(): Boolean
1519

20+
open fun getTileIcon(): Icon? = null
21+
1622
override fun onStartListening() {
1723
super.onStartListening()
1824
updateTile()
@@ -35,9 +41,19 @@ abstract class BaseTileService : TileService() {
3541
Tile.STATE_UNAVAILABLE
3642
}
3743
qsTile.label = getTileLabel()
38-
qsTile.subtitle = if (!hasPerm) "Missing permissions" else getTileSubtitle()
44+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
45+
qsTile.subtitle = if (!hasPerm) "Missing permissions" else getTileSubtitle()
46+
}
47+
val icon = getTileIcon()
48+
if (icon != null) {
49+
qsTile.icon = icon
50+
}
3951
qsTile.updateTile()
4052
}
4153

4254
protected abstract fun getTileState(): Int
4355
}
56+
57+
58+
59+

app/src/main/java/com/sameerasw/essentials/services/ScreenOffAccessibilityService.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ import android.os.Handler
1212
import android.os.Looper
1313
import android.view.WindowManager
1414
import android.view.View
15-
import androidx.core.content.ContextCompat
16-
import android.provider.Settings
1715
import com.sameerasw.essentials.utils.OverlayHelper
1816

1917
class ScreenOffAccessibilityService : AccessibilityService() {
@@ -71,11 +69,11 @@ class ScreenOffAccessibilityService : AccessibilityService() {
7169

7270
if (vibrator != null) {
7371
val prefs = getSharedPreferences("essentials_prefs", MODE_PRIVATE)
74-
val typeName = prefs.getString("haptic_feedback_type", HapticFeedbackType.SUBTLE.name)
72+
val typeName = prefs.getString("haptic_feedback_type", HapticFeedbackType.NONE.name)
7573
val feedbackType = try {
76-
HapticFeedbackType.valueOf(typeName ?: HapticFeedbackType.SUBTLE.name)
74+
HapticFeedbackType.valueOf(typeName ?: HapticFeedbackType.NONE.name)
7775
} catch (e: Exception) {
78-
HapticFeedbackType.SUBTLE
76+
HapticFeedbackType.NONE
7977
}
8078

8179
performHapticFeedback(vibrator, feedbackType)
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package com.sameerasw.essentials.services
2+
3+
import android.app.NotificationManager
4+
import android.content.BroadcastReceiver
5+
import android.content.Context
6+
import android.content.Intent
7+
import android.content.IntentFilter
8+
import android.graphics.drawable.Icon
9+
import android.media.AudioManager
10+
import android.os.Build
11+
import android.service.quicksettings.Tile
12+
import android.service.quicksettings.TileService
13+
import androidx.annotation.RequiresApi
14+
import com.sameerasw.essentials.R
15+
16+
@RequiresApi(Build.VERSION_CODES.N)
17+
class SoundModeTileService : TileService() {
18+
19+
private var latestAudioStateUpdate: Int? = null
20+
21+
private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
22+
override fun onReceive(context: Context, intent: Intent) {
23+
if (intent.action == AudioManager.RINGER_MODE_CHANGED_ACTION) {
24+
updateSoundTile()
25+
}
26+
}
27+
}
28+
29+
private fun updateSoundTile() {
30+
val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
31+
32+
if (latestAudioStateUpdate == audioManager.ringerMode) {
33+
latestAudioStateUpdate = null
34+
return
35+
}
36+
37+
if (qsTile == null) {
38+
return
39+
}
40+
41+
// Check if permission is granted
42+
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
43+
val hasPermission = notificationManager.isNotificationPolicyAccessGranted
44+
45+
if (!hasPermission) {
46+
qsTile.state = Tile.STATE_UNAVAILABLE
47+
qsTile.label = "Sound"
48+
qsTile.updateTile()
49+
return
50+
}
51+
52+
when (audioManager.ringerMode) {
53+
AudioManager.RINGER_MODE_NORMAL -> {
54+
qsTile.label = "Sound"
55+
qsTile.icon = Icon.createWithResource(this, R.drawable.rounded_volume_up_24)
56+
qsTile.state = Tile.STATE_INACTIVE
57+
}
58+
59+
AudioManager.RINGER_MODE_VIBRATE -> {
60+
qsTile.label = "Vibrate"
61+
qsTile.icon = Icon.createWithResource(this, R.drawable.rounded_mobile_vibrate_24)
62+
qsTile.state = Tile.STATE_ACTIVE
63+
}
64+
65+
AudioManager.RINGER_MODE_SILENT -> {
66+
qsTile.label = "Silent"
67+
qsTile.icon = Icon.createWithResource(this, R.drawable.rounded_volume_off_24)
68+
qsTile.state = Tile.STATE_ACTIVE
69+
}
70+
}
71+
72+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
73+
qsTile.subtitle = "Mode"
74+
}
75+
76+
qsTile.updateTile()
77+
}
78+
79+
override fun onClick() {
80+
super.onClick()
81+
82+
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
83+
if (!notificationManager.isNotificationPolicyAccessGranted) {
84+
return
85+
}
86+
87+
val prefs = getSharedPreferences("essentials_prefs", MODE_PRIVATE)
88+
val defaultOrder = listOf("Sound", "Vibrate", "Silent")
89+
val orderString = prefs.getString("sound_mode_order", defaultOrder.joinToString(",")) ?: defaultOrder.joinToString(",")
90+
val order = orderString.split(",")
91+
92+
val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
93+
94+
val currentMode = when (audioManager.ringerMode) {
95+
AudioManager.RINGER_MODE_NORMAL -> "Sound"
96+
AudioManager.RINGER_MODE_VIBRATE -> "Vibrate"
97+
AudioManager.RINGER_MODE_SILENT -> "Silent"
98+
else -> "Sound"
99+
}
100+
101+
val currentIndex = order.indexOf(currentMode)
102+
val nextIndex = (currentIndex + 1) % order.size
103+
val nextMode = order[nextIndex]
104+
105+
val nextRingerMode = when (nextMode) {
106+
"Sound" -> AudioManager.RINGER_MODE_NORMAL
107+
"Vibrate" -> AudioManager.RINGER_MODE_VIBRATE
108+
"Silent" -> AudioManager.RINGER_MODE_SILENT
109+
else -> AudioManager.RINGER_MODE_NORMAL
110+
}
111+
112+
audioManager.ringerMode = nextRingerMode
113+
114+
latestAudioStateUpdate = nextRingerMode
115+
116+
updateSoundTile()
117+
}
118+
119+
override fun onStartListening() {
120+
super.onStartListening()
121+
updateSoundTile()
122+
}
123+
124+
override fun onCreate() {
125+
super.onCreate()
126+
127+
this.registerReceiver(
128+
broadcastReceiver,
129+
IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION)
130+
)
131+
}
132+
133+
override fun onDestroy() {
134+
super.onDestroy()
135+
136+
try {
137+
this.unregisterReceiver(broadcastReceiver)
138+
} catch (e: Exception) {
139+
e.printStackTrace()
140+
}
141+
}
142+
143+
override fun onTileAdded() {
144+
super.onTileAdded()
145+
updateSoundTile()
146+
}
147+
148+
override fun onTileRemoved() {
149+
super.onTileRemoved()
150+
151+
if (qsTile == null) {
152+
return
153+
}
154+
155+
qsTile.state = Tile.STATE_UNAVAILABLE
156+
qsTile.updateTile()
157+
}
158+
}

0 commit comments

Comments
 (0)