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/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/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/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/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()) } } } 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..339987d 100644 --- a/app/src/main/java/com/maary/liveinpeace/service/ForegroundService.kt +++ b/app/src/main/java/com/maary/liveinpeace/service/ForegroundService.kt @@ -18,11 +18,13 @@ 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 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 +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 @@ -111,11 +112,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() // 启动前台服务,并立即更新一次通知状态 @@ -131,6 +135,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() { // 注册音频设备回调 @@ -139,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") @@ -169,6 +176,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 +216,6 @@ class ForegroundService : Service() { // 安全地反注册所有接收器和回调 safeUnregisterReceiver(volumeChangeReceiver) - safeUnregisterReceiver(sleepReceiver) try { audioManager.unregisterAudioDeviceCallback(audioDeviceCallback) } catch (e: Exception) { @@ -540,12 +550,6 @@ class ForegroundService : Service() { } } - private val sleepReceiver = object : SleepReceiver() { - override fun updateNotification(context: Context) { - updateForegroundNotification() - } - } - // --- 通知创建 --- private fun createForegroundNotification(context: Context): Notification { @@ -619,10 +623,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) 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