Skip to content

Commit bf77031

Browse files
malysclaude
andcommitted
release: prepare MG4 ABRP Uploader 1.0.3
- boot receiver now listens to QUICKBOOT_POWERON/LOCKED_BOOT_COMPLETED/MY_PACKAGE_REPLACED, not just BOOT_COMPLETED - fix credential check reading plaintext prefs instead of SecurePrefs (autostart never fired) - add autostart preference toggle in UI, defaulting on Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 7fbb8a6 commit bf77031

7 files changed

Lines changed: 111 additions & 5 deletions

File tree

‎app/build.gradle‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ android {
3434
applicationId "com.mg4.abrptelemetry"
3535
minSdk 27
3636
targetSdk 36
37-
versionCode 3
38-
versionName "1.0.2"
37+
versionCode 4
38+
versionName "1.0.3"
3939

4040
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
4141
}

‎app/src/main/AndroidManifest.xml‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,21 @@
4545
android:exported="false"
4646
android:foregroundServiceType="location" />
4747

48+
<!-- The head unit resumes far more often than it cold-boots, and announces the
49+
resume with QUICKBOOT_POWERON. Listening only for BOOT_COMPLETED is why
50+
uploading did not start with the car. MY_PACKAGE_REPLACED covers the other
51+
gap: an update stops the service and nothing else restarts it. -->
4852
<receiver
4953
android:name=".BootReceiver"
5054
android:exported="true">
5155
<intent-filter>
5256
<action android:name="android.intent.action.BOOT_COMPLETED" />
57+
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
58+
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
59+
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
60+
</intent-filter>
61+
<intent-filter>
62+
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
5363
</intent-filter>
5464
</receiver>
5565

‎app/src/main/java/com/mg4/abrptelemetry/BootReceiver.java‎

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,66 @@
44
import android.content.Context;
55
import android.content.Intent;
66
import android.content.SharedPreferences;
7+
import android.util.Log;
78

9+
import java.util.Arrays;
10+
import java.util.HashSet;
11+
import java.util.Set;
12+
13+
/**
14+
* Restarts uploading when the head unit powers up.
15+
*
16+
* BOOT_COMPLETED alone was not enough. An MG4's head unit rarely cold-boots: switching the
17+
* car on usually resumes it, and the resume is announced with QUICKBOOT_POWERON instead.
18+
* Users reported uploading never starting with the car, and this is why — the app was
19+
* waiting for a broadcast their car does not always send. The other MG4Suite apps already
20+
* listen to the whole family; this one now does too.
21+
*
22+
* MY_PACKAGE_REPLACED is in the list for a different reason: an update stops the service,
23+
* and nothing else would bring it back until the next power cycle.
24+
*/
825
public class BootReceiver extends BroadcastReceiver {
926

27+
private static final String TAG = "MG4ABRP.Boot";
28+
29+
private static final Set<String> START_ACTIONS = new HashSet<>(Arrays.asList(
30+
Intent.ACTION_BOOT_COMPLETED,
31+
Intent.ACTION_MY_PACKAGE_REPLACED,
32+
"android.intent.action.LOCKED_BOOT_COMPLETED",
33+
"android.intent.action.QUICKBOOT_POWERON",
34+
"com.htc.intent.action.QUICKBOOT_POWERON"
35+
));
36+
1037
@Override
1138
public void onReceive(Context context, Intent intent) {
12-
if (!Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) return;
39+
String action = intent.getAction();
40+
if (action == null || !START_ACTIONS.contains(action)) return;
1341

1442
SharedPreferences prefs = context.getSharedPreferences("abrp_prefs", Context.MODE_PRIVATE);
1543
boolean enabled = prefs.getBoolean("service_enabled", false);
16-
String token = prefs.getString("token", "").trim();
44+
boolean autostart = prefs.getBoolean(UploadSettings.KEY_AUTOSTART, UploadSettings.DEFAULT_AUTOSTART);
45+
46+
// Credentials come from SecurePrefs, not from the plaintext file. This receiver used
47+
// to read "token" out of abrp_prefs — where it has not lived since the credentials
48+
// were encrypted, because the migration deletes the plaintext copy. The check
49+
// therefore always failed, and uploading never started by itself on any car.
50+
SharedPreferences secure = SecurePrefs.get(context);
51+
boolean haveCreds = !secure.getString(SecurePrefs.KEY_TOKEN, "").trim().isEmpty()
52+
&& !secure.getString(SecurePrefs.KEY_API_KEY, "").trim().isEmpty();
53+
54+
if (!enabled || !autostart || !haveCreds) {
55+
Log.i(TAG, action + " ignored (enabled=" + enabled
56+
+ " autostart=" + autostart + " credentials=" + haveCreds + ")");
57+
return;
58+
}
1759

18-
if (enabled && !token.isEmpty()) {
60+
Log.i(TAG, action + " — starting upload service");
61+
try {
1962
context.startForegroundService(new Intent(context, AbrpUploadService.class));
63+
} catch (Exception e) {
64+
// A start refused this early is not worth crashing the boot broadcast over: the
65+
// next power cycle, or opening the app, starts it just the same.
66+
Log.w(TAG, "startForegroundService failed: " + e.getMessage());
2067
}
2168
}
2269
}

‎app/src/main/java/com/mg4/abrptelemetry/MainActivity.java‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class MainActivity extends AppCompatActivity {
4747
private TextInputLayout tokenLayout;
4848
private TextInputEditText tokenInput;
4949
private SwitchMaterial serviceSwitch;
50+
private SwitchMaterial autostartSwitch;
5051
private TextView statusText;
5152
private Button testButton;
5253
private View connectionStatusRow;
@@ -98,6 +99,7 @@ protected void onCreate(Bundle savedInstanceState) {
9899
tokenLayout = findViewById(R.id.token_layout);
99100
tokenInput = findViewById(R.id.token_input);
100101
serviceSwitch = findViewById(R.id.service_switch);
102+
autostartSwitch = findViewById(R.id.autostart_switch);
101103
statusText = findViewById(R.id.status_text);
102104
testButton = findViewById(R.id.test_button);
103105
connectionStatusRow = findViewById(R.id.connection_status_row);
@@ -132,6 +134,11 @@ protected void onCreate(Bundle savedInstanceState) {
132134
tokenInput.setText(securePrefs.getString(SecurePrefs.KEY_TOKEN, ""));
133135
serviceSwitch.setChecked(prefs.getBoolean("service_enabled", false));
134136

137+
autostartSwitch.setChecked(
138+
prefs.getBoolean(UploadSettings.KEY_AUTOSTART, UploadSettings.DEFAULT_AUTOSTART));
139+
autostartSwitch.setOnCheckedChangeListener((btn, checked) ->
140+
prefs.edit().putBoolean(UploadSettings.KEY_AUTOSTART, checked).apply());
141+
135142
// If the user wants the service running, kick it on every activity launch.
136143
// startForegroundService is idempotent — if the service is already up this
137144
// is a no-op aside from delivering a new intent. We don't trust
@@ -204,6 +211,8 @@ protected void onResume() {
204211
apiKeyInput.setText(savedApiKeyOrDefault());
205212
tokenInput.setText(securePrefs.getString(SecurePrefs.KEY_TOKEN, ""));
206213
serviceSwitch.setChecked(prefs.getBoolean("service_enabled", false));
214+
autostartSwitch.setChecked(
215+
prefs.getBoolean(UploadSettings.KEY_AUTOSTART, UploadSettings.DEFAULT_AUTOSTART));
207216
// Poll state and log only while the screen is up; onPause cancels it.
208217
uiHandler.post(uiRefresh);
209218
}

‎app/src/main/java/com/mg4/abrptelemetry/UploadSettings.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ final class UploadSettings {
1414
static final String KEY_INTERVAL_SEC = "upload_interval_sec";
1515
static final String KEY_BOOST_LOW_SOC = "boost_low_soc";
1616
static final String KEY_LOW_SOC_PERCENT = "low_soc_percent";
17+
static final String KEY_AUTOSTART = "autostart";
18+
19+
/**
20+
* On by default: an ABRP plan is only useful if it is fed from the moment the drive
21+
* starts, and nobody opens this app before setting off. Kept as a switch because a
22+
* service that starts itself must also be switchable off.
23+
*/
24+
static final boolean DEFAULT_AUTOSTART = true;
1725

1826
/**
1927
* Default cadence while driving. 60 s is roughly 1.5 km between points at motorway

‎app/src/main/res/layout/activity_main.xml‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,36 @@
270270

271271
</LinearLayout>
272272

273+
<!-- Redémarrage automatique. Personne n'ouvre cette application avant
274+
de partir : si l'envoi ne repart pas tout seul quand la voiture
275+
s'allume, il ne repart pas du tout. -->
276+
<LinearLayout
277+
android:layout_width="match_parent"
278+
android:layout_height="wrap_content"
279+
android:gravity="center_vertical"
280+
android:orientation="horizontal"
281+
android:minHeight="@dimen/touch_target">
282+
283+
<TextView
284+
android:layout_width="0dp"
285+
android:layout_height="wrap_content"
286+
android:layout_weight="1"
287+
android:text="@string/label_autostart"
288+
android:textAppearance="@style/Text.MG4.Body" />
289+
290+
<com.google.android.material.switchmaterial.SwitchMaterial
291+
android:id="@+id/autostart_switch"
292+
android:layout_width="wrap_content"
293+
android:layout_height="wrap_content" />
294+
295+
</LinearLayout>
296+
297+
<TextView
298+
android:layout_width="match_parent"
299+
android:layout_height="wrap_content"
300+
android:text="@string/hint_autostart"
301+
android:textAppearance="@style/Text.MG4.Secondary" />
302+
273303
<TextView
274304
android:id="@+id/status_text"
275305
android:layout_width="wrap_content"

‎app/src/main/res/values/strings.xml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838

3939
<string name="card_service">Upload Service</string>
4040
<string name="label_upload_enabled">Enable background uploading</string>
41+
<string name="label_autostart">Start automatically with the car</string>
42+
<string name="hint_autostart">Uploading resumes on its own when the head unit powers up. Turn this off to start it by hand from this screen.</string>
4143

4244
<string name="status_stopped">Service not running</string>
4345

0 commit comments

Comments
 (0)