From 6b7d1fb211d95930c690956c73c44ec60408b13c Mon Sep 17 00:00:00 2001 From: Maary <24504742+Steve-Mr@users.noreply.github.com> Date: Sun, 21 Sep 2025 13:57:58 +0800 Subject: [PATCH 1/5] Refactor SleepTimer update to use ForegroundService This commit refactors the SleepTimer update mechanism. Instead of using a BroadcastReceiver (`SleepReceiver`) to handle `BROADCAST_ACTION_SLEEPTIMER_UPDATE`, this action is now directly sent to and handled by `ForegroundService`. Key changes: - In `MuteMediaReceiver`, intents with `BROADCAST_ACTION_SLEEPTIMER_UPDATE` are now directed to `ForegroundService` using `startService()`. - `ForegroundService` now handles `BROADCAST_ACTION_SLEEPTIMER_UPDATE` in its `onStartCommand` method, triggering `updateForegroundNotification()`. - The `sleepReceiver` instance and its registration/unregistration in `ForegroundService` have been removed/commented out as it's no longer needed for this specific update. --- .../liveinpeace/receiver/MuteMediaReceiver.kt | 13 +++++-- .../liveinpeace/service/ForegroundService.kt | 38 ++++++++++++------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/maary/liveinpeace/receiver/MuteMediaReceiver.kt b/app/src/main/java/com/maary/liveinpeace/receiver/MuteMediaReceiver.kt index 5bdf0bb..d994bb0 100644 --- a/app/src/main/java/com/maary/liveinpeace/receiver/MuteMediaReceiver.kt +++ b/app/src/main/java/com/maary/liveinpeace/receiver/MuteMediaReceiver.kt @@ -29,15 +29,20 @@ class MuteMediaReceiver: BroadcastReceiver() { if (p1?.action == BROADCAST_ACTION_SLEEPTIMER_CANCEL || p1?.action == BROADCAST_ACTION_SLEEPTIMER_INCREMENT || p1?.action == BROADCAST_ACTION_SLEEPTIMER_DECREMENT) { + p0?.handle(p1) - val intent = Intent(BROADCAST_ACTION_SLEEPTIMER_UPDATE) - p0?.sendBroadcast(intent) + + val intent = Intent(p0, ForegroundService::class.java) + intent.action = BROADCAST_ACTION_SLEEPTIMER_UPDATE + p0?.startService(intent) } if (p1?.action == BROADCAST_ACTION_SLEEPTIMER_TOGGLE) { p0?.toggle() - val intent = Intent(BROADCAST_ACTION_SLEEPTIMER_UPDATE) - p0?.sendBroadcast(intent) + + val intent = Intent(p0, ForegroundService::class.java) + intent.action = BROADCAST_ACTION_SLEEPTIMER_UPDATE + p0?.startService(intent) } } } \ No newline at end of file diff --git a/app/src/main/java/com/maary/liveinpeace/service/ForegroundService.kt b/app/src/main/java/com/maary/liveinpeace/service/ForegroundService.kt index e9a7079..96e5b4e 100644 --- a/app/src/main/java/com/maary/liveinpeace/service/ForegroundService.kt +++ b/app/src/main/java/com/maary/liveinpeace/service/ForegroundService.kt @@ -23,6 +23,7 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.graphics.drawable.IconCompat import com.maary.liveinpeace.Constants +import com.maary.liveinpeace.Constants.Companion.BROADCAST_ACTION_SLEEPTIMER_UPDATE import com.maary.liveinpeace.DeviceTimer import com.maary.liveinpeace.R import com.maary.liveinpeace.SleepNotification.find @@ -32,7 +33,7 @@ import com.maary.liveinpeace.database.ConnectionDao import com.maary.liveinpeace.database.ConnectionRoomDatabase import com.maary.liveinpeace.database.PreferenceRepository import com.maary.liveinpeace.receiver.MuteMediaReceiver -import com.maary.liveinpeace.receiver.SleepReceiver +//import com.maary.liveinpeace.receiver.SleepReceiver import com.maary.liveinpeace.receiver.VolumeReceiver import dagger.hilt.android.AndroidEntryPoint import jakarta.inject.Inject @@ -141,12 +142,12 @@ class ForegroundService : Service() { registerReceiver(volumeChangeReceiver, volumeFilter) // 注册休眠定时器更新接收器 - val sleepFilter = IntentFilter(Constants.BROADCAST_ACTION_SLEEPTIMER_UPDATE) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - registerReceiver(sleepReceiver, sleepFilter, RECEIVER_NOT_EXPORTED) - } else { - registerReceiver(sleepReceiver, sleepFilter) - } +// val sleepFilter = IntentFilter(Constants.BROADCAST_ACTION_SLEEPTIMER_UPDATE) +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { +// registerReceiver(sleepReceiver, sleepFilter, RECEIVER_NOT_EXPORTED) +// } else { +// registerReceiver(sleepReceiver, sleepFilter) +// } } @SuppressLint("MissingPermission") @@ -169,6 +170,10 @@ class ForegroundService : Service() { Log.d(TAG, "Mute media action received.") handleMuteMedia() } + BROADCAST_ACTION_SLEEPTIMER_UPDATE -> { + Log.d(TAG, "Sleep timer update action received via onStartCommand.") + updateForegroundNotification() + } } // 确保服务被重新创建时,通知内容是最新的 @@ -205,7 +210,7 @@ class ForegroundService : Service() { // 安全地反注册所有接收器和回调 safeUnregisterReceiver(volumeChangeReceiver) - safeUnregisterReceiver(sleepReceiver) +// safeUnregisterReceiver(sleepReceiver) try { audioManager.unregisterAudioDeviceCallback(audioDeviceCallback) } catch (e: Exception) { @@ -540,11 +545,18 @@ class ForegroundService : Service() { } } - private val sleepReceiver = object : SleepReceiver() { - override fun updateNotification(context: Context) { - updateForegroundNotification() - } - } +// private val sleepReceiver = object : SleepReceiver() { +// override fun onReceive(context: Context, intent: Intent) { +// // 在父类的 onReceive 中添加 Log +// Log.d("ForegroundService", "sleepReceiver received action: ${intent.action}") // <-- 添加这行 Log +// super.onReceive(context, intent) +// } +// +// override fun updateNotification(context: Context) { +// Log.d("ForegroundService", "sleepReceiver is calling updateForegroundNotification()") // <-- 添加这行 Log +// updateForegroundNotification() +// } +// } // --- 通知创建 --- From 83fcc01fac7f4328b98707a5b5a04ea2d5bdf07a Mon Sep 17 00:00:00 2001 From: Maary <24504742+Steve-Mr@users.noreply.github.com> Date: Sun, 21 Sep 2025 19:11:14 +0800 Subject: [PATCH 2/5] Replace debounce with throttle in VolumeReceiver The `VolumeReceiver` previously used a debounce mechanism to handle volume changes. This has been replaced with a throttle mechanism. The constant `DEBOUNCE_TIME_MS` has been renamed to `THROTTLE_TIME_MS` and its value changed from `500` to `100L`. The `VolumeReceiver`'s `onReceive` method now checks if the time since the last update is greater than or equal to `THROTTLE_TIME_MS`. If it is, `updateNotification` is called immediately. Otherwise, the event is ignored. The `Handler` and delayed `postDelayed` call have been removed. --- app/src/main/java/com/maary/liveinpeace/Constants.kt | 2 +- .../com/maary/liveinpeace/receiver/VolumeReceiver.kt | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/maary/liveinpeace/Constants.kt b/app/src/main/java/com/maary/liveinpeace/Constants.kt index d68b973..4531ee8 100644 --- a/app/src/main/java/com/maary/liveinpeace/Constants.kt +++ b/app/src/main/java/com/maary/liveinpeace/Constants.kt @@ -44,7 +44,7 @@ class Constants { // 提醒时间 const val ALERT_TIME: Long = 2*60*60*1000 // 延后时间 - const val DEBOUNCE_TIME_MS = 500 + const val THROTTLE_TIME_MS = 100L // 不同通知的 GROUP ID const val ID_NOTIFICATION_GROUP_FORE = "LIP_notification_group_foreground" const val ID_NOTIFICATION_GROUP_ALERTS = "LIP_notification_group_alerts" diff --git a/app/src/main/java/com/maary/liveinpeace/receiver/VolumeReceiver.kt b/app/src/main/java/com/maary/liveinpeace/receiver/VolumeReceiver.kt index 6aae656..a097dbe 100644 --- a/app/src/main/java/com/maary/liveinpeace/receiver/VolumeReceiver.kt +++ b/app/src/main/java/com/maary/liveinpeace/receiver/VolumeReceiver.kt @@ -3,23 +3,17 @@ package com.maary.liveinpeace.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.os.Handler -import android.os.Looper -import com.maary.liveinpeace.Constants.Companion.DEBOUNCE_TIME_MS +import com.maary.liveinpeace.Constants.Companion.THROTTLE_TIME_MS abstract class VolumeReceiver : BroadcastReceiver() { private var lastUpdateTime: Long = 0 - private val handler = Handler(Looper.getMainLooper()) override fun onReceive(context: Context, intent: Intent) { - if (intent.action == "android.media.VOLUME_CHANGED_ACTION" - ) { + if (intent.action == "android.media.VOLUME_CHANGED_ACTION") { val now = System.currentTimeMillis() - if (now - lastUpdateTime >= DEBOUNCE_TIME_MS) { + if (now - lastUpdateTime >= THROTTLE_TIME_MS) { lastUpdateTime = now updateNotification(context) - } else { - handler.postDelayed({ onReceive(context, intent) }, DEBOUNCE_TIME_MS.toLong()) } } } From c872d1995ead2443e0a8eb198e912be6f33db577 Mon Sep 17 00:00:00 2001 From: Maary <24504742+Steve-Mr@users.noreply.github.com> Date: Sun, 21 Sep 2025 19:12:36 +0800 Subject: [PATCH 3/5] Preload volume icons for notification This commit introduces a preloading mechanism for volume icons used in notifications. Instead of dynamically looking up the resource ID for each volume percentage (0-100) every time a notification is generated, this change: - Creates a `SparseIntArray` called `volumeIconMap`. - Populates `volumeIconMap` during the `onCreate` lifecycle method of the `ForegroundService`. It iterates from 0 to 100, finds the drawable resource ID for each `num_X` (where X is the percentage), and stores it in the map. - Modifies `generateNotificationIcon` to directly retrieve the resource ID from `volumeIconMap` using the current volume percentage. This optimization aims to improve performance by reducing repeated resource lookups, especially when volume changes frequently and notifications need to be updated. A default value of 0 is used if a specific icon is not found in the map. --- .../liveinpeace/service/ForegroundService.kt | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/maary/liveinpeace/service/ForegroundService.kt b/app/src/main/java/com/maary/liveinpeace/service/ForegroundService.kt index 96e5b4e..d7e2765 100644 --- a/app/src/main/java/com/maary/liveinpeace/service/ForegroundService.kt +++ b/app/src/main/java/com/maary/liveinpeace/service/ForegroundService.kt @@ -18,6 +18,7 @@ import android.media.AudioManager import android.os.Build import android.os.IBinder import android.util.Log +import android.util.SparseIntArray import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat @@ -112,11 +113,14 @@ class ForegroundService : Service() { private val protectionJobs = ConcurrentHashMap() private val deviceMapMutex = Mutex() + private val volumeIconMap = SparseIntArray() + override fun onCreate() { super.onCreate() Log.d(TAG, "Service creating...") initializeDependencies() + preloadVolumeIcons() registerReceiversAndCallbacks() // 启动前台服务,并立即更新一次通知状态 @@ -132,6 +136,17 @@ class ForegroundService : Service() { volumeComment = resources.getStringArray(R.array.array_volume_comment) } + @SuppressLint("DiscouragedApi") + private fun preloadVolumeIcons() { + for (i in 0..100) { + val resourceName = "num_$i" + val resourceId = resources.getIdentifier(resourceName, "drawable", packageName) + if (resourceId != 0) { + volumeIconMap.put(i, resourceId) + } + } + } + @SuppressLint("UnspecifiedRegisterReceiverFlag") private fun registerReceiversAndCallbacks() { // 注册音频设备回调 @@ -631,10 +646,9 @@ class ForegroundService : Service() { ) } - @SuppressLint("DiscouragedApi") private fun generateNotificationIcon(context: Context, volumePercent: Int, volumeLevel: Int): IconCompat { - val resourceName = "num_${volumePercent}" - val resourceId = resources.getIdentifier(resourceName, "drawable", context.packageName) + // 直接从 SparseIntArray 中查找,速度极快 + val resourceId = volumeIconMap.get(volumePercent, 0) // 第二个参数是找不到时的默认值 return if (resourceId != 0) { IconCompat.createWithResource(this, resourceId) From c254ee05a0a674a6eea5c62943d7cf0ebc0b2d85 Mon Sep 17 00:00:00 2001 From: Maary <24504742+Steve-Mr@users.noreply.github.com> Date: Sun, 21 Sep 2025 19:16:41 +0800 Subject: [PATCH 4/5] Remove SleepReceiver and related broadcast logic The `SleepReceiver` class and its associated broadcast registration and handling logic within `ForegroundService` have been removed. The functionality for updating notifications based on sleep timer events is no longer handled through this broadcast mechanism. --- .../liveinpeace/receiver/SleepReceiver.kt | 18 --------------- .../liveinpeace/service/ForegroundService.kt | 23 ------------------- 2 files changed, 41 deletions(-) delete mode 100644 app/src/main/java/com/maary/liveinpeace/receiver/SleepReceiver.kt diff --git a/app/src/main/java/com/maary/liveinpeace/receiver/SleepReceiver.kt b/app/src/main/java/com/maary/liveinpeace/receiver/SleepReceiver.kt deleted file mode 100644 index cab5619..0000000 --- a/app/src/main/java/com/maary/liveinpeace/receiver/SleepReceiver.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.maary.liveinpeace.receiver - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import com.maary.liveinpeace.Constants.Companion.BROADCAST_ACTION_SLEEPTIMER_UPDATE - -abstract class SleepReceiver: BroadcastReceiver() { - - override fun onReceive(context: Context, intent: Intent) { - if (intent.action == BROADCAST_ACTION_SLEEPTIMER_UPDATE - ) { - updateNotification(context) - } - } - - abstract fun updateNotification(context: Context) -} \ No newline at end of file diff --git a/app/src/main/java/com/maary/liveinpeace/service/ForegroundService.kt b/app/src/main/java/com/maary/liveinpeace/service/ForegroundService.kt index d7e2765..339987d 100644 --- a/app/src/main/java/com/maary/liveinpeace/service/ForegroundService.kt +++ b/app/src/main/java/com/maary/liveinpeace/service/ForegroundService.kt @@ -34,7 +34,6 @@ import com.maary.liveinpeace.database.ConnectionDao import com.maary.liveinpeace.database.ConnectionRoomDatabase import com.maary.liveinpeace.database.PreferenceRepository import com.maary.liveinpeace.receiver.MuteMediaReceiver -//import com.maary.liveinpeace.receiver.SleepReceiver import com.maary.liveinpeace.receiver.VolumeReceiver import dagger.hilt.android.AndroidEntryPoint import jakarta.inject.Inject @@ -155,14 +154,6 @@ class ForegroundService : Service() { // 注册音量变化接收器 val volumeFilter = IntentFilter("android.media.VOLUME_CHANGED_ACTION") registerReceiver(volumeChangeReceiver, volumeFilter) - - // 注册休眠定时器更新接收器 -// val sleepFilter = IntentFilter(Constants.BROADCAST_ACTION_SLEEPTIMER_UPDATE) -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { -// registerReceiver(sleepReceiver, sleepFilter, RECEIVER_NOT_EXPORTED) -// } else { -// registerReceiver(sleepReceiver, sleepFilter) -// } } @SuppressLint("MissingPermission") @@ -225,7 +216,6 @@ class ForegroundService : Service() { // 安全地反注册所有接收器和回调 safeUnregisterReceiver(volumeChangeReceiver) -// safeUnregisterReceiver(sleepReceiver) try { audioManager.unregisterAudioDeviceCallback(audioDeviceCallback) } catch (e: Exception) { @@ -560,19 +550,6 @@ class ForegroundService : Service() { } } -// private val sleepReceiver = object : SleepReceiver() { -// override fun onReceive(context: Context, intent: Intent) { -// // 在父类的 onReceive 中添加 Log -// Log.d("ForegroundService", "sleepReceiver received action: ${intent.action}") // <-- 添加这行 Log -// super.onReceive(context, intent) -// } -// -// override fun updateNotification(context: Context) { -// Log.d("ForegroundService", "sleepReceiver is calling updateForegroundNotification()") // <-- 添加这行 Log -// updateForegroundNotification() -// } -// } - // --- 通知创建 --- private fun createForegroundNotification(context: Context): Notification { From 854344992499ef540384cac1f1a6774c78269852 Mon Sep 17 00:00:00 2001 From: Maary <24504742+Steve-Mr@users.noreply.github.com> Date: Sun, 21 Sep 2025 19:34:33 +0800 Subject: [PATCH 5/5] Update dependencies and versionName This commit updates various dependencies to their latest versions and modifies the `versionName`. --- app/build.gradle | 34 +++++++++++++++++----------------- build.gradle | 10 +++++----- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d89358e..50414a0 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,7 +25,7 @@ android { minSdk 31 targetSdk 36 versionCode 5 - versionName "2025.09.07" + versionName "2025.09.21" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -140,42 +140,42 @@ dependencies { implementation 'com.google.android.material:material:1.13.0' implementation 'androidx.constraintlayout:constraintlayout:2.2.1' - implementation 'androidx.activity:activity-ktx:1.10.1' + implementation 'androidx.activity:activity-ktx:1.11.0' implementation 'androidx.databinding:databinding-runtime:8.13.0' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.9.3' - implementation 'androidx.activity:activity-compose:1.10.1' - implementation platform('androidx.compose:compose-bom:2025.08.01') + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.9.4' + implementation 'androidx.activity:activity-compose:1.11.0' + implementation platform('androidx.compose:compose-bom:2025.09.00') implementation 'androidx.compose.ui:ui' implementation 'androidx.compose.ui:ui-graphics' implementation 'androidx.compose.ui:ui-tooling-preview' - implementation 'androidx.compose.material3:material3:1.4.0-beta03' - androidTestImplementation platform('androidx.compose:compose-bom:2025.08.01') + implementation 'androidx.compose.material3:material3:1.4.0-rc01' + androidTestImplementation platform('androidx.compose:compose-bom:2025.09.00') androidTestImplementation 'androidx.compose.ui:ui-test-junit4' debugImplementation 'androidx.compose.ui:ui-tooling' debugImplementation 'androidx.compose.ui:ui-test-manifest' // LiveData - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.9.3" - implementation "androidx.lifecycle:lifecycle-common-java8:2.9.3" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.3" - implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.9.3" - implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.9.3' + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.9.4" + implementation "androidx.lifecycle:lifecycle-common-java8:2.9.4" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.4" + implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.9.4" + implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.9.4' implementation "com.google.dagger:hilt-android:2.57.1" ksp "com.google.dagger:hilt-compiler:2.57.1" - implementation "androidx.room:room-runtime:2.7.2" - annotationProcessor "androidx.room:room-compiler:2.7.2" - implementation 'androidx.room:room-ktx:2.7.2' - ksp "androidx.room:room-compiler:2.7.2" + implementation "androidx.room:room-runtime:2.8.0" + annotationProcessor "androidx.room:room-compiler:2.8.0" + implementation 'androidx.room:room-ktx:2.8.0' + ksp "androidx.room:room-compiler:2.8.0" implementation "androidx.datastore:datastore-preferences:1.2.0-alpha02" implementation("com.google.accompanist:accompanist-permissions:0.37.3") implementation "androidx.compose.material:material-icons-extended:1.7.8" - implementation "androidx.work:work-runtime-ktx:2.10.3" + implementation "androidx.work:work-runtime-ktx:2.10.4" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.3.0' diff --git a/build.gradle b/build.gradle index 2c95c4c..8ff9ca7 100755 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.11.1' apply false - id 'com.android.library' version '8.11.1' apply false - id 'org.jetbrains.kotlin.android' version '2.2.10' apply false - id 'com.google.devtools.ksp' version "2.2.10-2.0.2" apply false - id 'org.jetbrains.kotlin.plugin.compose' version '2.2.10' apply false + id 'com.android.application' version '8.11.2' apply false + id 'com.android.library' version '8.11.2' apply false + id 'org.jetbrains.kotlin.android' version '2.2.20' apply false + id 'com.google.devtools.ksp' version "2.2.20-2.0.3" apply false + id 'org.jetbrains.kotlin.plugin.compose' version '2.2.20' apply false id 'com.google.dagger.hilt.android' version '2.57.1' apply false } \ No newline at end of file