Skip to content

Commit 8457d34

Browse files
authored
v2.6.7
Fix : The new advanced shortcut system now allows you to keep the default shortcut associated with the key. Added : HVAC control is now available when creating or editing profiles. Added : Shortcuts can now be linked to a specific profile, allowing for different shortcuts depending on the profile the user has applied. Added : There is a new “Regen Cycle” shortcut that lets you choose which regeneration modes to cycle through (instead of cycling through 1, 2, 3, A, the user can select just 2 to quickly alternate between them without having to switch profiles). Added : Dealer/Service mode. Replaces the “Automatic Profile Application” option. This allows you to disable all MG4Control-related features and notifications to prevent the user from having to uninstall the app, for example, when undergoing a technical inspection. Added : 3 parameters related to “CUSTOM” driving mode: Horsepower, Steering, and Pedal Force + Profile Management. Added : It is now possible to control the multi-profile pop-up menu using the steering wheel joystick if MG4Control has been granted accessibility permissions. Added : Surpport the audio feature for all firmware (based on the READY status because the doors status is not available for others firmware) Added : A new automation feature that automatically closes the windows (taken from Winclose and adapted for all firmware versions). Added : 2 new shortcut to open or close all windows. Added : Text size option (Standard / Large / Extra large). profiles. It replaces the separate front and rear defrost buttons; existing profiles are migrated automatically. Added: Turkish language
2 parents f7ed650 + 932c286 commit 8457d34

149 files changed

Lines changed: 24690 additions & 6639 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/security/permission-allowlist.txt‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ android.permission.WRITE_SETTINGS
1515
android.permission.BLUETOOTH
1616
android.permission.BLUETOOTH_CONNECT
1717
android.car.permission.CAR_ENERGY
18+
# Lecture seule des informations STATIQUES du véhicule. Utilisée pour une seule propriété :
19+
# INFO_EV_BATTERY_CAPACITY, la capacité de la batterie, proposée comme valeur par défaut du
20+
# réglage de l'onglet Statistiques (hardware/EnergyReader.kt). Aucune écriture, aucune commande
21+
# du véhicule, rien qui varie en roulant. N'ouvre PAS le VIN, qui relève d'une permission
22+
# distincte (CAR_IDENTIFICATION).
23+
android.car.permission.CAR_INFO
1824
android.car.permission.CAR_POWERTRAIN
1925
android.car.permission.CAR_VENDOR_EXTENSION
2026
android.car.permission.CONTROL_CAR_CLIMATE

‎.github/workflows/beta.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ jobs:
157157
| Variant | File | Network | Auto-update |
158158
|---|---|---|---|
159159
| **Online** | `MG4Control-online-*.apk` | yes | yes |
160-
| **Offline** | `MG4Control-offline-*.apk` | none | no |
160+
| **Offline** | `MG4Control-secure-*-offline.apk` | none | no |
161161
162162
# Purge des anciennes betas. Trois garde-fous, dans cet ordre :
163163
# 1. on ne touche QU'AUX prereleases (select .prerelease==true) — une erreur de filtre

‎.github/workflows/release.yml‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ jobs:
5959
- name: Collect APKs
6060
run: |
6161
mkdir -p dist
62-
find app/build/outputs/apk -name "MG4Control-*-*.apk" -exec cp {} dist/ \;
62+
# Deux motifs volontairement larges : le nom de l'offline porte un préfixe qui le
63+
# fait trier après l'online dans les assets GitHub (voir app/build.gradle.kts).
64+
find app/build/outputs/apk -name "MG4Control-*.apk" -exec cp {} dist/ \;
6365
ls -la dist/
6466
6567
# /!\ --min-sdk-version 23 is REQUIRED for this report to tell the truth. Without it,
@@ -107,6 +109,6 @@ jobs:
107109
| Variant | File | Network | Auto-update | Target |
108110
|---|---|---|---|---|
109111
| **Online** | `MG4Control-online-*.apk` | yes | yes | standard use |
110-
| **Offline** 🔒 | `MG4Control-offline-*.apk` | none (network perms removed) | no | hardened on-vehicle deployment |
112+
| **Offline** 🔒 | `MG4Control-secure-*-offline.apk` | none (network perms removed) | no | hardened on-vehicle deployment |
111113
112114
> The offline APK has a distinct applicationId (`com.mg4.control.offline`) and can coexist with the online one.

‎README.md‎

Lines changed: 374 additions & 20 deletions
Large diffs are not rendered by default.

‎app/build.gradle.kts‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ android {
1111
applicationId = "com.mg4.control"
1212
minSdk = 28
1313
targetSdk = 34
14-
versionCode = 18
14+
versionCode = 19
1515
// Suffixe injecte par la CI beta : -Pmg4.versionSuffix=-beta42 produit "2.6.6-beta42".
1616
// L'APK installe annonce alors EXACTEMENT ce que dit le tag de la release, sans quoi
1717
// l'OTA reproposerait la meme mise a jour indefiniment.
18-
versionName = "2.6.6" + (project.findProperty("mg4.versionSuffix") as String? ?: "")
18+
versionName = "2.6.7" + (project.findProperty("mg4.versionSuffix") as String? ?: "")
1919
}
2020

2121
// Signature avec la clé plateforme de la ROM (requise par sharedUserId=android.uid.system).
@@ -97,7 +97,22 @@ android {
9797
outputs.all {
9898
val output = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl
9999
if (variant.buildType.name == "release") {
100-
output.outputFileName = "MG4Control-${variant.flavorName}-${variant.versionName}.apk"
100+
// ⚠️ LE NOM DE L'APK OFFLINE EST FONCTIONNEL, PAS COSMÉTIQUE.
101+
//
102+
// L'API GitHub renvoie les assets d'une release triés PAR NOM, et les clients
103+
// antérieurs à la 2.6.3 téléchargent le PREMIER .apk qu'ils y trouvent, sans
104+
// regarder lequel. « offline » précédant « online » alphabétiquement, ces
105+
// versions installaient l'APK offline — qui porte un applicationId distinct
106+
// (com.mg4.control.offline) et apparaît donc comme une SECONDE application à
107+
// côté de la leur. Cas réellement constaté d'une 2.6.0 passée à la 2.6.6.
108+
//
109+
// Le préfixe « secure » place l'offline après l'online dans ce tri. Ne pas le
110+
// retirer, et ne jamais donner à l'offline un nom contenant « online » : c'est
111+
// sur cette sous-chaîne que selectApk distingue les deux depuis la 2.6.3.
112+
output.outputFileName = when (variant.flavorName) {
113+
"offline" -> "MG4Control-secure-${variant.versionName}.apk"
114+
else -> "MG4Control-${variant.flavorName}-${variant.versionName}.apk"
115+
}
101116
}
102117
}
103118
}

‎app/lint.xml‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Règles Lint désactivées pour MG4Control.
4+
5+
L'app ne tourne que sur l'écran de l'infodivertissement MG4 : paysage fixe, pas de clavier,
6+
pas de saisie automatique, langues latines uniquement, et une charte visuelle choisie
7+
(tailles de texte réglables par l'utilisateur, interrupteurs Android natifs). Les règles
8+
ci-dessous ne signalent que des préférences de style ou des micro-optimisations d'affichage
9+
sans effet mesurable sur ce matériel : elles noyaient les vrais avertissements.
10+
11+
Ce fichier ne change NI le code compilé NI le comportement de l'app : uniquement ce que Lint
12+
signale. Restent volontairement actives les règles qui peuvent révéler un défaut réel :
13+
textes non traduits (HardcodedText), pluriels, accessibilité, sécurité, permissions.
14+
-->
15+
<lint>
16+
<!-- Style : Switch Android natif conservé exprès (charte actuelle, validée sur véhicule). -->
17+
<issue id="UseSwitchCompatOrMaterialXml" severity="ignore" />
18+
<issue id="UseSwitchCompatOrMaterialCode" severity="ignore" />
19+
20+
<!-- Style : boutons de dialogue/barres volontairement encadrés, plus lisibles en voiture. -->
21+
<issue id="ButtonStyle" severity="ignore" />
22+
23+
<!-- Style : petites étiquettes voulues ; la taille du texte est réglable (Réglages › Interface). -->
24+
<issue id="SmallSp" severity="ignore" />
25+
26+
<!-- Pas de saisie de formulaire ni de gestionnaire de mots de passe sur l'écran voiture. -->
27+
<issue id="Autofill" severity="ignore" />
28+
29+
<!-- Aucune langue de droite à gauche parmi les 6 langues de l'app. -->
30+
<issue id="RtlSymmetry" severity="ignore" />
31+
32+
<!-- Micro-optimisations de mise en page, sans effet mesurable sur l'écran de l'infodivertissement. -->
33+
<issue id="DisableBaselineAlignment" severity="ignore" />
34+
<issue id="Overdraw" severity="ignore" />
35+
<issue id="NestedWeights" severity="ignore" />
36+
<issue id="UselessParent" severity="ignore" />
37+
<issue id="TooManyViews" severity="ignore" />
38+
<issue id="TooDeepLayout" severity="ignore" />
39+
<issue id="UseCompoundDrawables" severity="ignore" />
40+
</lint>

‎app/src/main/AndroidManifest.xml‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools"
34
android:sharedUserId="android.uid.system">
45

56
<!-- Permissions — same as original DriveHub Dort -->
@@ -13,14 +14,19 @@
1314

1415
<!-- Consommation de donnees : signature|privileged, accessible car l app est signee
1516
avec la cle plateforme. Sans elle, NetworkStatsManager leve une SecurityException. -->
16-
<uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
17+
<uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY"
18+
tools:ignore="ProtectedPermissions" />
1719
<!-- Pilotage des sessions média (piste suivante, lecture/pause). Voie du launcher
1820
d'origine sur les firmwares A9, et seule voie pour CarPlay/Android Auto sur
1921
SWI68/165. signature|privileged : obtenue parce que l'app tourne en
2022
android.uid.system. -->
21-
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
23+
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"
24+
tools:ignore="ProtectedPermissions" />
2225
<!-- Vehicle control permissions (Android Automotive / SAIC) -->
2326
<uses-permission android:name="android.car.permission.CAR_ENERGY" />
27+
<!-- Capacité de la batterie (INFO_EV_BATTERY_CAPACITY) : propriété statique du véhicule,
28+
déclarée sous CAR_INFO dans les services véhicule SWI133, SWI165 et SWI131. -->
29+
<uses-permission android:name="android.car.permission.CAR_INFO" />
2430
<uses-permission android:name="android.car.permission.CAR_POWERTRAIN" />
2531
<uses-permission android:name="android.car.permission.CAR_VENDOR_EXTENSION" />
2632
<uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />

‎app/src/main/java/com/mg4/control/MG4App.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class MG4App : Application() {
3131
}
3232

3333
// ── Migration + initialisation du thème ───────────────────────────────
34-
val prefs = getSharedPreferences("mg4_settings", Context.MODE_PRIVATE)
34+
val prefs = getSharedPreferences("mg4_settings", MODE_PRIVATE)
3535

3636
if (!prefs.contains(ThemeHelper.PREF_THEME_MODE)) {
3737
// Migration depuis l'ancien booléen "dark_theme" (version < 2.x)

‎app/src/main/java/com/mg4/control/MainActivity.kt‎

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import kotlinx.coroutines.launch
2525
import kotlinx.coroutines.withContext
2626
import com.mg4.control.update.UpdateChecker
2727
import com.mg4.control.update.UpdateDialogManager
28+
import com.mg4.control.update.UpdateNotifier
2829
import com.mg4.control.util.FirmwareInfo
2930
import com.mg4.control.util.LocaleHelper
3031
import com.mg4.control.util.ThemeHelper
@@ -86,10 +87,28 @@ class MainActivity : AppCompatActivity() {
8687
setupDiagnosticUnlock()
8788
checkUnknownFirmware()
8889
navigateToDefaultScreen(savedInstanceState)
89-
checkForUpdates()
90+
// Ouverture venue du popup véhicule : la release est déjà connue, on affiche le
91+
// dialogue tout de suite plutôt que de refaire l'aller-retour réseau.
92+
val depuisPopup = UpdateNotifier.readFrom(intent)
93+
if (depuisPopup != null) UpdateDialogManager.show(this, depuisPopup)
94+
else checkForUpdates()
9095
checkProfileRestore()
9196
}
9297

98+
/**
99+
* L'application était déjà au premier plan quand le popup véhicule a été touché.
100+
*
101+
* L'intent est lancé en FLAG_ACTIVITY_SINGLE_TOP : dans ce cas Android ne recrée pas
102+
* l'activité et [onCreate] n'est jamais rappelé — sans cette surcharge, « Installer la MAJ »
103+
* ramènerait l'application au premier plan sans rien ouvrir du tout.
104+
*/
105+
override fun onNewIntent(intent: Intent) {
106+
super.onNewIntent(intent)
107+
setIntent(intent)
108+
val info = UpdateNotifier.readFrom(intent) ?: return
109+
if (!isFinishing && !isDestroyed) UpdateDialogManager.show(this, info)
110+
}
111+
93112
// ── Restauration des profils depuis la sauvegarde (après réinstallation) ──────
94113
// Ne se déclenche QUE si l'app n'a aucun profil local (vraie désinstallation /
95114
// effacement de données — pas une simple mise à jour qui conserve les données).
@@ -150,10 +169,10 @@ class MainActivity : AppCompatActivity() {
150169

151170
// ── Navigation vers l'écran par défaut au démarrage ─────────────────────
152171

153-
private fun navigateToDefaultScreen(savedInstanceState: android.os.Bundle?) {
172+
private fun navigateToDefaultScreen(savedInstanceState: Bundle?) {
154173
// Ne naviguer que si c'est un vrai démarrage (pas une rotation / recreate)
155174
if (savedInstanceState != null) return
156-
val prefs = getSharedPreferences("mg4_settings", android.content.Context.MODE_PRIVATE)
175+
val prefs = getSharedPreferences("mg4_settings", MODE_PRIVATE)
157176
when (prefs.getString("default_screen", "dashboard")) {
158177
"profiles" -> navController.navigate(R.id.profileFragment)
159178
"shortcuts" -> navController.navigate(R.id.shortcutsFragment)
@@ -165,7 +184,7 @@ class MainActivity : AppCompatActivity() {
165184

166185
private fun checkForUpdates() {
167186
if (BuildConfig.OFFLINE) return // build offline : aucune vérif réseau
168-
val prefs = getSharedPreferences("mg4_settings", android.content.Context.MODE_PRIVATE)
187+
val prefs = getSharedPreferences("mg4_settings", MODE_PRIVATE)
169188
if (!prefs.getBoolean("auto_check_update", true)) return
170189

171190
UpdateChecker.check(
@@ -215,12 +234,13 @@ class MainActivity : AppCompatActivity() {
215234

216235
private fun setupNavButtons() {
217236
val btnAutomation = findViewById<MaterialButton>(R.id.btn_nav_automation)
237+
val btnStats = findViewById<MaterialButton>(R.id.btn_nav_stats)
218238
val btnAudio = findViewById<MaterialButton>(R.id.btn_nav_audio)
219239
val btnShortcuts = findViewById<MaterialButton>(R.id.btn_nav_shortcuts)
220240
val btnProfiles = findViewById<MaterialButton>(R.id.btn_nav_profiles)
221241
val btnSettings = findViewById<MaterialButton>(R.id.btn_nav_settings)
222242

223-
// Bouton Audio : contrôle vendor caradapter dispo uniquement sur A9 → masqué ailleurs.
243+
// Bouton Audio : visible partout où le volume média est pilotable (ancien SDK et A9).
224244
if (MG4Hardware.hasAudioControl()) {
225245
btnAudio.setOnClickListener {
226246
when (navController.currentDestination?.id) {
@@ -239,6 +259,13 @@ class MainActivity : AppCompatActivity() {
239259
}
240260
}
241261

262+
btnStats.setOnClickListener {
263+
when (navController.currentDestination?.id) {
264+
R.id.statsFragment -> navController.popBackStack(R.id.dashboardFragment, false)
265+
else -> navController.navigate(R.id.statsFragment)
266+
}
267+
}
268+
242269
btnShortcuts.setOnClickListener {
243270
when (navController.currentDestination?.id) {
244271
R.id.shortcutsFragment -> navController.popBackStack(R.id.dashboardFragment, false)
@@ -266,6 +293,9 @@ class MainActivity : AppCompatActivity() {
266293
btnAutomation.backgroundTintList = android.content.res.ColorStateList.valueOf(
267294
if (destination.id == R.id.automationFragment) accent else inactive
268295
)
296+
btnStats.backgroundTintList = android.content.res.ColorStateList.valueOf(
297+
if (destination.id == R.id.statsFragment) accent else inactive
298+
)
269299
btnAudio.backgroundTintList = android.content.res.ColorStateList.valueOf(
270300
if (destination.id == R.id.audioFragment) accent else inactive
271301
)
@@ -299,7 +329,8 @@ class MainActivity : AppCompatActivity() {
299329
R.id.btn_pick_de to "de",
300330
R.id.btn_pick_es to "es",
301331
R.id.btn_pick_pt to "pt",
302-
R.id.btn_pick_it to "it"
332+
R.id.btn_pick_it to "it",
333+
R.id.btn_pick_tr to "tr"
303334
)
304335
buttons.forEach { (viewId, code) ->
305336
dialogView.findViewById<MaterialButton>(viewId).setOnClickListener {

0 commit comments

Comments
 (0)