@@ -365,7 +365,7 @@ object MG4Hardware {
365365 // Démarrage auto du watcher porte au boot si la feature est activée (tous firmwares).
366366 startDoorWatcherIfEnabled()
367367 // Connexion Car établie même si la feature est OFF → la sonde Diagnostic peut lire les portes.
368- if (hasDoorVolumeFeature ()) connectCarProperty()
368+ if (hasDoorDetection ()) connectCarProperty()
369369
370370 AppLogger .i(TAG , " ========================================" )
371371 }
@@ -3565,7 +3565,7 @@ object MG4Hardware {
35653565 return gen == FirmwareInfo .Gen .SWI133 || gen == FirmwareInfo .Gen .SWI68 || gen == FirmwareInfo .Gen .SWI165
35663566 }
35673567
3568- /* * Onglet Audio (baisse volume à l'ouverture de porte ) : uniquement là où c' est fonctionnel . */
3568+ /* * Onglet Audio (baisse de volume en quittant la voiture ) : partout où le volume est pilotable . */
35693569 fun hasAudioControl (): Boolean = hasDoorVolumeFeature()
35703570
35713571 private const val DESCRIPTOR_CARADAPTER = " com.saicmotor.carapi.ICarAdapterService"
@@ -4741,13 +4741,20 @@ object MG4Hardware {
47414741 }
47424742 }
47434743
4744- /* * Baisse du volume à l'ouverture de porte : détection DLOCK_DOOR_OPEN_STS via CarPropertyManager.
4745- * Lisible/fonctionnel uniquement sur SWI132 et SWI133 ; ailleurs le prop n'est pas exposé à l'app. */
4746- fun hasDoorVolumeFeature (): Boolean {
4744+ /* * Détection réelle de l'ouverture des portes ( DLOCK_DOOR_OPEN_STS via CarPropertyManager) :
4745+ * lisible uniquement sur SWI132 et SWI133 ; ailleurs le prop n'est pas exposé à l'app. */
4746+ fun hasDoorDetection (): Boolean {
47474747 val gen = FirmwareInfo .getGeneration()
47484748 return gen == FirmwareInfo .Gen .SWI132 || gen == FirmwareInfo .Gen .SWI133
47494749 }
47504750
4751+ /* *
4752+ * Baisse du volume en quittant la voiture : partout où le volume média est pilotable (ancien SDK
4753+ * et A9). Déclencheur : la porte là où elle est lisible ([hasDoorDetection]), sinon la sortie du
4754+ * mode READY en P ([ReadyWatcher]), qui correspond sur MG4 à l'ouverture de la porte conducteur.
4755+ */
4756+ fun hasDoorVolumeFeature (): Boolean = isOldSdkSound() || isA9Sound()
4757+
47514758 private fun doorVolumeEnabled (): Boolean {
47524759 // Mode Garage : une portière qui fait chuter le volume est exactement le genre de
47534760 // comportement inexpliqué qu'on ne veut pas laisser observer.
@@ -4779,19 +4786,79 @@ object MG4Hardware {
47794786 fun startDoorVolumeWatcher () {
47804787 if (! hasDoorVolumeFeature()) return
47814788 sDoorWatcherOn = true
4782- connectCarProperty()
4789+ if (hasDoorDetection()) connectCarProperty() else ReadyWatcher .add(readyVolumeListener) // add idempotent
47834790 }
47844791
47854792 fun stopDoorVolumeWatcher () {
47864793 sDoorWatcherOn = false // poll conservé ; on ne déclenche plus la baisse
4794+ if (! hasDoorDetection()) ReadyWatcher .remove(readyVolumeListener)
47874795 AppLogger .i(TAG , " DoorVolumeWatcher: déclenchement désactivé" )
47884796 }
47894797
4798+ /* *
4799+ * Firmwares sans porte lisible : sortie de READY en P = départ du conducteur → baisse ; retour en
4800+ * READY → restauration si l'utilisateur l'a choisie. La sortie à l'extinction voiture occupée baisse
4801+ * aussi le volume (accepté). Rapport illisible : on baisse quand même (aucun risque, au contraire
4802+ * des vitres). Le premier état connu n'est pas une transition, sauf pour une restauration en
4803+ * attente (écran redémarré entre la baisse et le retour en READY).
4804+ */
4805+ private val readyVolumeListener = ReadyWatcher .Listener { ready, firstRead ->
4806+ if (! sDoorWatcherOn) return @Listener
4807+ if (ready) {
4808+ restoreMediaVolumeAfterDrop(if (firstRead) " READY à l'activation" else " retour en READY" )
4809+ return @Listener
4810+ }
4811+ if (firstRead) return @Listener
4812+ if (! doorVolumeEnabled()) return @Listener // inclut le Mode Garage
4813+ val inPark = isVehicleInPark()
4814+ if (inPark == false ) {
4815+ AppLogger .i(DOORWATCH_TAG , " sortie de READY hors P → volume inchangé" )
4816+ return @Listener
4817+ }
4818+ dropMediaVolumeForExit(" sortie de READY (P=${inPark ? : " illisible" } )" )
4819+ }
4820+
4821+ /* * Volume avant la baisse, gardé en préférences : la restauration survit à un redémarrage de l'écran. */
4822+ private const val KEY_VOLUME_BEFORE_DROP = " door_volume_before_drop"
4823+
4824+ private fun dropMediaVolumeForExit (origin : String ) {
4825+ val level = doorVolumeLevel()
4826+ CoroutineScope (Dispatchers .IO ).launch {
4827+ val before = getMediaVolume()
4828+ sVolumeBeforeDrop = before
4829+ if (before >= 0 ) sAppContext?.getSharedPreferences(" mg4_settings" , 0 )?.edit()
4830+ ?.putInt(KEY_VOLUME_BEFORE_DROP , before)?.apply ()
4831+ val ok = setMediaVolume(level)
4832+ AppLogger .i(DOORWATCH_TAG , " $origin → vol $before →$level = $ok " )
4833+ }
4834+ }
4835+
4836+ private fun restoreMediaVolumeAfterDrop (origin : String ) {
4837+ val prefs = sAppContext?.getSharedPreferences(" mg4_settings" , 0 )
4838+ val restore = sVolumeBeforeDrop.takeIf { it >= 0 } ? : prefs?.getInt(KEY_VOLUME_BEFORE_DROP , - 1 ) ? : - 1
4839+ sVolumeBeforeDrop = - 1
4840+ prefs?.edit()?.remove(KEY_VOLUME_BEFORE_DROP )?.apply ()
4841+ if (restore < 0 ) return
4842+ if (! doorRestoreEnabled()) {
4843+ AppLogger .i(DOORWATCH_TAG , " $origin → restauration désactivée (volume laissé tel quel)" )
4844+ return
4845+ }
4846+ CoroutineScope (Dispatchers .IO ).launch {
4847+ val ok = setMediaVolume(restore)
4848+ AppLogger .i(DOORWATCH_TAG , " $origin → restauration vol $restore = $ok " )
4849+ }
4850+ }
4851+
47904852 /* * Sonde du bouton Diagnostic : logge le volume + l'état des portes à l'instant du clic. */
47914853 fun runDoorVolumeDiag () {
47924854 AppLogger .i(VOL_TAG , " ── DIAG (bouton Diagnostic) ──" )
47934855 getMediaVolumeMax()
47944856 getMediaVolume()
4857+ if (! hasDoorDetection()) {
4858+ AppLogger .i(DOORWATCH_TAG , " DIAG pas de porte lisible sur ${FirmwareInfo .getGeneration()} → READY brut = " +
4859+ " ${readEptReadyRaw() ? : " illisible" } (surveillance ${if (ReadyWatcher .ready != null ) " active" else " inactive" } )" )
4860+ return
4861+ }
47954862 connectCarProperty() // idempotent ; normalement déjà connecté depuis l'init
47964863 registerDoorCallback() // re-tente la souscription si pas encore posée
47974864 probeDoorSnapshot()
@@ -5562,21 +5629,9 @@ object MG4Hardware {
55625629 val triggerAreas = doorTriggerAreas()
55635630 val anyOpen = triggerAreas.any { sDoorReadLast[it] == 1 }
55645631 if (anyOpen && ! sAnyFrontOpenPrev) {
5565- val level = doorVolumeLevel()
5566- CoroutineScope (Dispatchers .IO ).launch {
5567- sVolumeBeforeDrop = getMediaVolume()
5568- val ok = setMediaVolume(level)
5569- AppLogger .i(DOORWATCH_TAG , " porte ouverte → vol $sVolumeBeforeDrop →$level = $ok " )
5570- }
5632+ dropMediaVolumeForExit(" porte ouverte" )
55715633 } else if (! anyOpen && sAnyFrontOpenPrev) {
5572- val restore = sVolumeBeforeDrop
5573- sVolumeBeforeDrop = - 1
5574- if (doorRestoreEnabled() && restore >= 0 ) {
5575- CoroutineScope (Dispatchers .IO ).launch {
5576- val ok = setMediaVolume(restore)
5577- AppLogger .i(DOORWATCH_TAG , " porte fermée → restauration vol $restore = $ok " )
5578- }
5579- }
5634+ restoreMediaVolumeAfterDrop(" porte fermée" )
55805635 }
55815636 sAnyFrontOpenPrev = anyOpen
55825637 }
0 commit comments