-
Notifications
You must be signed in to change notification settings - Fork 2
feat: track connection volume and add restore on disconnect option #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9ddacd0
88cf6a1
9dab16b
71d1912
a8b3df8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -36,6 +36,8 @@ class PreferenceRepository @Inject constructor(@ApplicationContext context: Cont | |||||||||
| val PREF_VISIBLE_IN_LAUNCHER = booleanPreferencesKey(Constants.PREF_HIDE_IN_LAUNCHER) | ||||||||||
| val PREF_EAR_PROTECTION_THRESHOLD_MAX = intPreferencesKey(Constants.PREF_EAR_PROTECTION_THRESHOLD_MAX) | ||||||||||
| val PREF_EAR_PROTECTION_THRESHOLD_MIN = intPreferencesKey(Constants.PREF_EAR_PROTECTION_THRESHOLD_MIN) | ||||||||||
| val PREF_RESTORE_VOLUME = booleanPreferencesKey(Constants.PREF_RESTORE_VOLUME) | ||||||||||
| val PREF_LAST_SYSTEM_VOLUME = intPreferencesKey(Constants.PREF_LAST_SYSTEM_VOLUME) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| fun getWatchingState(): Flow<Boolean> { | ||||||||||
|
|
@@ -126,4 +128,26 @@ class PreferenceRepository @Inject constructor(@ApplicationContext context: Cont | |||||||||
| pref[PREF_EAR_PROTECTION_THRESHOLD_MAX] = range.last | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // 获取设置状态 | ||||||||||
| fun isVolumeRestoreEnabled(): Flow<Boolean> { | ||||||||||
| return datastore.data.map { pref -> | ||||||||||
| pref[PREF_RESTORE_VOLUME] ?: false | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // 切换设置 | ||||||||||
| suspend fun setVolumeRestoreEnabled(enabled: Boolean) { | ||||||||||
| datastore.edit { preferences -> preferences[PREF_RESTORE_VOLUME] = enabled } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| fun getLastSystemVolume(): Flow<Int> = datastore.data | ||||||||||
| .map { preferences -> preferences[PREF_LAST_SYSTEM_VOLUME] ?: -1 } | ||||||||||
|
Comment on lines
+144
to
+145
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function uses I recommend adding a constant like
Suggested change
|
||||||||||
|
|
||||||||||
| // 更新系统音量记录 | ||||||||||
| suspend fun saveLastSystemVolume(volume: Int) { | ||||||||||
| datastore.edit { preferences -> | ||||||||||
| preferences[PREF_LAST_SYSTEM_VOLUME] = volume | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -43,6 +43,7 @@ import kotlinx.coroutines.Dispatchers | |||||
| import kotlinx.coroutines.Job | ||||||
| import kotlinx.coroutines.SupervisorJob | ||||||
| import kotlinx.coroutines.cancel | ||||||
| import kotlinx.coroutines.delay | ||||||
| import kotlinx.coroutines.flow.first | ||||||
| import kotlinx.coroutines.isActive | ||||||
| import kotlinx.coroutines.launch | ||||||
|
|
@@ -114,6 +115,8 @@ class ForegroundService : Service() { | |||||
|
|
||||||
| private val volumeIconMap = SparseIntArray() | ||||||
|
|
||||||
| private var lastKnownSystemVolumeCache: Int = -1 | ||||||
|
|
||||||
| override fun onCreate() { | ||||||
| super.onCreate() | ||||||
| Log.d(TAG, "Service creating...") | ||||||
|
|
@@ -324,14 +327,19 @@ class ForegroundService : Service() { | |||||
| var wasAdded = false | ||||||
| deviceMapMutex.withLock { | ||||||
| if (!deviceMap.containsKey(deviceName)) { | ||||||
| val savedVolume = preferenceRepository.getLastSystemVolume().first() | ||||||
| val startVol = if (savedVolume != -1) savedVolume else getVolumePercentage() | ||||||
|
|
||||||
| Log.d(CALLBACK_TAG, "Device Added: $deviceName") | ||||||
| deviceMap[deviceName] = Connection( | ||||||
| name = deviceName, | ||||||
| type = deviceInfo.type, | ||||||
| connectedTime = System.currentTimeMillis(), | ||||||
| disconnectedTime = null, | ||||||
| duration = null, | ||||||
| date = LocalDate.now().toString() | ||||||
| date = LocalDate.now().toString(), | ||||||
| startVolume = startVol, | ||||||
| endVolume = null | ||||||
| ) | ||||||
| wasAdded = true | ||||||
|
|
||||||
|
|
@@ -365,13 +373,54 @@ class ForegroundService : Service() { | |||||
| disconnectedTime = disconnectedTime, | ||||||
| duration = duration | ||||||
| ) | ||||||
|
|
||||||
| // if (preferenceRepository.isVolumeRestoreEnabled().first()) { | ||||||
| // connection.startVolume?.let { startVol -> | ||||||
| // Log.d(TAG, "Restoring volume to $startVol% for disconnect of $deviceName") | ||||||
| // val targetIndex = percentageToVolumeIndex(startVol) | ||||||
| // // 使用 FLAG_SHOW_UI 可以让用户看到音量变化的滑块,方便调试确认 | ||||||
| // audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, targetIndex, AudioManager.FLAG_SHOW_UI) | ||||||
| // } | ||||||
| // } | ||||||
|
|
||||||
| if (duration > Constants.ALERT_TIME) { | ||||||
| val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager | ||||||
| notificationManager.cancel(Constants.ID_NOTIFICATION_ALERT) | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // [第二步] 在锁释放后:等待系统切换、读取音量、恢复音量 | ||||||
| // 这样做可以避免阻塞其他设备的连接/断开处理 | ||||||
| if (connectionToSave != null) { | ||||||
| // [核心修改] 延迟 600ms,等待系统将音频输出切换回扬声器并更新音量状态 | ||||||
| // 具体的毫秒数可能因机型而异,500-800ms 通常是安全的 | ||||||
| delay(600) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using a fixed For future improvement, you might consider a more robust mechanism. For example, you could listen for the |
||||||
|
|
||||||
| // 此时获取的音量应该是系统切换后的音量(即“断开后”的真实状态) | ||||||
| val currentVol = getVolumePercentage() | ||||||
|
|
||||||
| // 更新 endVolume | ||||||
| val finalConnection = connectionToSave.copy(endVolume = currentVol) | ||||||
|
|
||||||
| // [音量恢复逻辑] | ||||||
| // 再次检查是否所有设备都已断开。 | ||||||
| // 如果此时用户迅速插入了另一个耳机,deviceMap 就不为空,我们就不应该执行“恢复扬声器音量”的操作,以免干扰新设备。 | ||||||
| val isMapEmpty = deviceMapMutex.withLock { deviceMap.isEmpty() } | ||||||
|
|
||||||
| if (isMapEmpty && preferenceRepository.isVolumeRestoreEnabled().first()) { | ||||||
| finalConnection.startVolume?.let { startVol -> | ||||||
| Log.d(CALLBACK_TAG, "Restoring volume to $startVol% for disconnect of $deviceName") | ||||||
| // 使用之前定义的辅助函数 percentageToVolumeIndex | ||||||
| val targetIndex = percentageToVolumeIndex(startVol) | ||||||
| audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, targetIndex, AudioManager.FLAG_SHOW_UI) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| return finalConnection | ||||||
| } | ||||||
|
|
||||||
| return connectionToSave | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The statement To improve code clarity and explicitly show that
Suggested change
|
||||||
| } | ||||||
|
|
||||||
|
|
@@ -442,6 +491,19 @@ class ForegroundService : Service() { | |||||
| Log.w(TAG, "Cannot update notification: Permission denied.") | ||||||
| return | ||||||
| } | ||||||
|
|
||||||
| if (deviceMap.isEmpty()) { | ||||||
| val currentVolume = getVolumePercentage() | ||||||
|
|
||||||
| if (currentVolume != lastKnownSystemVolumeCache) { | ||||||
| lastKnownSystemVolumeCache = currentVolume | ||||||
| serviceScope.launch { | ||||||
| preferenceRepository.saveLastSystemVolume(currentVolume) | ||||||
| } | ||||||
| Log.d(TAG, "Updated internal system volume record: $currentVolume%") | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| NotificationManagerCompat.from(this).notify( | ||||||
| Constants.ID_NOTIFICATION_FOREGROUND, | ||||||
| createForegroundNotification(this) | ||||||
|
|
@@ -532,6 +594,11 @@ class ForegroundService : Service() { | |||||
| return if (maxVolume > 0) 100 * currentVolume / maxVolume else 0 | ||||||
| } | ||||||
|
|
||||||
| private fun percentageToVolumeIndex(percent: Int): Int { | ||||||
| val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) | ||||||
| return ((percent * maxVolume) + 50) / 100 | ||||||
| } | ||||||
|
|
||||||
| private fun getVolumeLevel(percent: Int): Int { | ||||||
| return when (percent) { | ||||||
| 0 -> 0 | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block can be simplified. You can remove the intermediate
startVolStringandendVolStringvariables, asgetStringcan directly accept theInt?values and will calltoString()on them. This makes the code more concise and idiomatic.