Skip to content

Commit c7934a4

Browse files
committed
fix: Banner + USB + Recording Service issue after switching
1 parent 52cb5ba commit c7934a4

4 files changed

Lines changed: 57 additions & 4 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,11 @@ Community mod for the MG4 EV (AAOS 9, pre-2026 facelift) that replaces the stock
6060

6161
<p align="center">
6262
<img src="images/blinker_cam_v2.gif" alt="Turn signal overlay" width="48%">
63-
&nbsp;
6463
<img src="images/dashcam_demo_v3.gif" alt="Dashcam mode" width="48%">
64+
6565
</p>
6666

67-
<p align="right">
67+
<p align="center">
6868
<a href="https://youtu.be/Rzb_Owc_RT0">▶ Full demo on YouTube</a>
6969
</p>
7070

‎app/src/main/java/com/drivehub/kamera/dashcam/DashcamSettingsController.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,13 @@ private void bindStorageGroup(SharedPreferences prefs, StorageViews views) {
249249
if (g.getChildAt(i).getId() == checkedId) {
250250
DashcamStorageManager.setStorageTarget(prefs, i);
251251
refreshStorageStatus(prefs, views);
252+
// USB-only without USB makes the recording loop end with a fatal error
253+
// and stopSelf(); the service is then gone until the user toggles the
254+
// dashcam off+on. Switching the target should be enough — kick the
255+
// service back up if it's idle while the dashcam is enabled.
256+
if (prefs.getBoolean(KEY_ENABLED, false) && !RecordingService.isRunning()) {
257+
RecordingService.startIfDashcamEnabled(activity);
258+
}
252259
break;
253260
}
254261
}

‎app/src/main/java/com/drivehub/kamera/dashcam/DashcamStorageManager.java‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public final class DashcamStorageManager {
4747

4848
private static final String USB_RECORDS_DIR_NAME = "dashcam";
4949
private static final String WRITE_PROBE_FILE_NAME = ".dashcam_write_probe";
50+
private static final File LEGACY_STORAGE_ROOT = new File("/storage");
5051

5152
public enum UsbState {
5253
NOT_CHECKED, // INTERNAL_ONLY mode: USB is deliberately ignored
@@ -192,6 +193,18 @@ private static final class UsbCandidate {
192193
*/
193194
private static UsbProbe probeUsb(Context context) {
194195
List<UsbCandidate> candidates = findUsbCandidates(context);
196+
if (candidates.isEmpty()) {
197+
// AAOS 9 head units typically don't surface OTG/USB volumes via StorageManager:
198+
// the OS mounts them under /mnt/media_rw/... without registering them with the
199+
// MediaProvider, so getExternalFilesDirs() returns nothing usable. Fall back to a
200+
// direct /storage scan, which is how every dashcam-style automotive app has to
201+
// handle this in practice.
202+
candidates = findLegacyStorageCandidates();
203+
if (!candidates.isEmpty()) {
204+
Log.i(TAG, "StorageManager exposed no removable volumes; using /storage scan ("
205+
+ candidates.size() + " candidate(s))");
206+
}
207+
}
195208
if (candidates.isEmpty()) {
196209
return new UsbProbe(UsbState.NO_MEDIUM, null);
197210
}
@@ -256,6 +269,27 @@ private static List<UsbCandidate> findUsbCandidates(Context context) {
256269
return new ArrayList<>(deduped.values());
257270
}
258271

272+
/**
273+
* Last-resort enumeration: list every directory under /storage that isn't the emulated
274+
* primary volume or the "self" symlink. No StorageManager metadata, no isRemovable hints,
275+
* just whatever the user can mount and read. Suitability is decided by the write probe.
276+
*/
277+
private static List<UsbCandidate> findLegacyStorageCandidates() {
278+
List<UsbCandidate> candidates = new ArrayList<>();
279+
File[] children = LEGACY_STORAGE_ROOT.listFiles();
280+
if (children == null) {
281+
return candidates;
282+
}
283+
for (File child : children) {
284+
if (child == null || !child.isDirectory()) continue;
285+
String name = child.getName();
286+
if (name.isEmpty() || "emulated".equals(name) || "self".equals(name)) continue;
287+
if (!child.canRead()) continue;
288+
candidates.add(new UsbCandidate(child, name, looksLikeUsb(child, name)));
289+
}
290+
return candidates;
291+
}
292+
259293
private static List<UsbCandidate> prioritizeUsbCandidates(List<UsbCandidate> candidates) {
260294
List<UsbCandidate> usbLike = new ArrayList<>();
261295
List<UsbCandidate> genericRemovable = new ArrayList<>();

‎app/src/main/java/com/drivehub/kamera/signal/SignalService.java‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,20 @@ private void startOemLifecycleMonitor() {
348348
oemLifecycleThread.start();
349349
oemLifecycleHandler = new Handler(oemLifecycleThread.getLooper());
350350
oemLifecycleMonitoring = true;
351-
lastOemLifecycle = readStringSystemProperty(OEM_LIFECYCLE_PROP);
352-
DevRuntimeLog.add("SignalService", "OEM lifecycle monitor started");
351+
// The AVM sysprop is sticky across reboots — if the previous session ended with "Start"
352+
// (crash, force-kill) and AVM is no longer actually in the foreground, treating that
353+
// stale value as the baseline causes the next genuine open→close cycle to fire a
354+
// "resume" without a preceding "pause". Reset stale Starts to DestroyEnd so the next
355+
// real Start triggers the pause path correctly.
356+
String initialState = readStringSystemProperty(OEM_LIFECYCLE_PROP);
357+
if (OEM_LIFECYCLE_START.equals(initialState) && !isOemAvmInForeground()) {
358+
DevRuntimeLog.add("SignalService",
359+
"OEM lifecycle: stale 'Start' on monitor start — resetting baseline");
360+
initialState = OEM_LIFECYCLE_DESTROYED;
361+
}
362+
lastOemLifecycle = initialState;
363+
DevRuntimeLog.add("SignalService",
364+
"OEM lifecycle monitor started, initial='" + initialState + "'");
353365
oemLifecycleHandler.post(new Runnable() {
354366
@Override
355367
public void run() {

0 commit comments

Comments
 (0)