A small helpdesk system: an office Pixel running a patched GrapheneOS Dialer answers technician calls, records both call legs as separate channels, and a Ruby backend transcribes them through Whisper and files an AI summary against the caller. Squashed to a single commit for sharing. No credentials are included; secrets live outside the repo in /etc/helpdesk/env on the server or a gitignored .claude/env.local locally. See .claude/env.local.example for the shape. Start at README.md, then docs/architecture.md.
65 lines
2.4 KiB
Java
65 lines
2.4 KiB
Java
/*
|
|
* Helpdesk Dialer patch — per-call correlation registry.
|
|
* Part of the com.android.dialer.helpdesk overlay (see components/dialer-patch).
|
|
*/
|
|
package com.android.dialer.helpdesk;
|
|
|
|
import java.util.Map;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.UUID;
|
|
|
|
/**
|
|
* Maps a live {@code DialerCall} (by its Dialer id) to our correlation {@code call_id} (UUIDv4),
|
|
* minted the first time the call is seen (at RINGING), plus per-call flags. The {@code call_id} is
|
|
* the single key that stitches the webhooks, the recording upload, the transcript and the reverse
|
|
* commands together. Thread-safe (touched from the Telecom/InCall callback thread).
|
|
*/
|
|
public final class CallIdRegistry {
|
|
|
|
public static final class Entry {
|
|
public final String callId = UUID.randomUUID().toString();
|
|
// Helpdesk: the call's start edge was announced (/calls/incoming for inbound, /calls/outgoing for
|
|
// outbound). Prevents re-firing the once-only start webhook on every onCallListChange tick.
|
|
public volatile boolean startReported;
|
|
public volatile boolean answeredReported;
|
|
public volatile String recordingUuid;
|
|
}
|
|
|
|
private final ConcurrentHashMap<String, Entry> byDialerId = new ConcurrentHashMap<>();
|
|
|
|
// Process-wide singleton. Shared by the in-call CallEventEmitter/RecordingBridge AND the always-on
|
|
// HelpdeskCommandService poller, so a reverse command that arrives between calls (e.g. `dial`) and one
|
|
// that arrives during a call (answer/mute/hold) both resolve against the same call_id <-> DialerCall map.
|
|
private static final CallIdRegistry INSTANCE = new CallIdRegistry();
|
|
|
|
public static CallIdRegistry get() {
|
|
return INSTANCE;
|
|
}
|
|
|
|
/** Get-or-create the entry, minting the call_id on first sight. */
|
|
public Entry forCall(String dialerId) {
|
|
return byDialerId.computeIfAbsent(dialerId, k -> new Entry());
|
|
}
|
|
|
|
public Entry peek(String dialerId) {
|
|
return byDialerId.get(dialerId);
|
|
}
|
|
|
|
public void remove(String dialerId) {
|
|
byDialerId.remove(dialerId);
|
|
}
|
|
|
|
/** Reverse lookup: the DialerCall id for our correlation call_id (used to apply reverse commands). */
|
|
public String dialerIdForCallId(String callId) {
|
|
if (callId == null) {
|
|
return null;
|
|
}
|
|
for (Map.Entry<String, Entry> e : byDialerId.entrySet()) {
|
|
if (callId.equals(e.getValue().callId)) {
|
|
return e.getKey();
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|