/* * Helpdesk Dialer patch — build/provisioning config. * Part of the com.android.dialer.helpdesk overlay (see components/dialer-patch). */ package com.android.dialer.helpdesk; /** * Backend endpoint + device credentials for the helpdesk integration. * *

Values resolve in precedence order: {@code persist.helpdesk.} (runtime-settable, survives * reboot) → {@code ro.helpdesk.} (baked at build/provisioning time) → dev default. The dev defaults * match the backend prototype (components/backend) so a userdebug build talks to a local server out of * the box. * *

The {@code persist.*} tier exists so a flashed device can be pointed at a different backend * without a rebuild — {@code ro.*} is write-once at init, so on a dev build you'd otherwise have * to re-flash to change the URL. So bring-up is just * {@code adb shell setprop persist.helpdesk.base_url https://:8443/api/v1} and a reboot (this is * how the production phone is pointed at moje.al.army's device door). * NOTE: setting {@code persist.helpdesk.*} on-device also needs a property_contexts entry granting the * setter domain access (the sepolicy-patch handles this); reading is unrestricted. For a fully locked * production build, bake {@code ro.helpdesk.*} into product build.prop instead. * TODO(provisioning): move the secret to Keystore-wrapped storage before the device is locked. */ public final class HelpdeskConfig { private HelpdeskConfig() {} public static String baseUrl() { // Precedence: explicit override (persist.* runtime / ro.* build) → mDNS-discovered backend on the LAN // (no hardcoded IP — the phone finds it on whatever WiFi it's on) → dev default. String v = get("persist.helpdesk.base_url"); if (v.isEmpty()) { v = get("ro.helpdesk.base_url"); } if (!v.isEmpty()) { return v; } String discovered = HelpdeskDiscovery.get().baseUrl(); if (discovered != null && !discovered.isEmpty()) { return discovered; } return "http://10.0.2.2:4000/api/v1"; } public static String deviceToken() { return provisioned("device_token", "dev-device-token"); } public static String deviceSecret() { return provisioned("device_secret", "dev-device-secret"); } /** Fail-closed: on a production ("user") build, refuse the repo-public dev default — an unprovisioned * device must NOT authenticate with a known credential. Dev builds keep the default for convenience. */ private static String provisioned(String suffix, String devDefault) { String v = resolve(suffix, devDefault); if (v.equals(devDefault) && !isDevBuild()) { android.util.Log.e("HelpdeskConfig", "device " + suffix + " not provisioned on a production build — refusing the dev default"); return ""; // empty → auth fails loudly instead of silently using the public dev credential } return v; } private static boolean isDevBuild() { String t = android.os.Build.TYPE; // "user" = production; "userdebug"/"eng" = dev return "userdebug".equals(t) || "eng".equals(t); } public static String deviceId() { return resolve("device_id", "stallion-dev"); } // ---- call recording audio quality ------------------------------------------------------------- // These exist as properties, not constants, because every one of them can only be judged by making a // real call and reading the transcript, and a rebuild-and-reflash per experiment is hours. With these // a single image can test the lot: `adb shell setprop persist.helpdesk.rec_bitrate 96000`, place a // call, score it with components/transcription-worker/bench/ab.rb. // Defaults reproduce the stock behaviour exactly, so an unset device records as it always has. /** Output format id: 0 = AAC/m4a (stock), 1 = AMR-WB, 2 = LPCM WAV (lossless, ~16x the bytes). */ public static int recordingFormat() { return intProp("rec_format", -1); // -1 = "not set", caller keeps the user's own preference } /** * AAC bitrate in bits per second. Stock is 16000 for a 16 kHz stream, i.e. about ONE BIT PER SAMPLE, * which is aggressive enough to mangle ordinary words: production transcripts turn "balíček" into * "badíček" and "zmeškaný" into "myštěný", and both Whisper models make the same mistakes, which is * what points at the audio rather than the model. Ignored by the WAV and AMR-WB formats. */ public static int recordingBitRate() { return intProp("rec_bitrate", 0); // 0 = "not set", keep the format's own value } /** * Log whether VOICE_UPLINK and VOICE_DOWNLINK can be captured on this device. Off by default; it * opens and immediately releases two AudioRecords, which is harmless but pointless in normal use. * *

This is a feasibility probe for per-speaker recording. Google Dialer captures uplink and * downlink as two separate AudioRecords and interleaves them into a 2-channel WAV, which makes * caller and callee exactly separable instead of leaving a diariser to guess. We cannot do that until * we know those sources are capturable here, and that is a property of the device's audio HAL and * sepolicy, not something the source can answer. */ public static boolean recordingProbeSources() { return "1".equals(resolve("rec_probe_sources", "0")); } /** * Record the two call legs as separate channels instead of one pre-mixed stream: left = uplink * (operator), right = downlink (caller). Off by default. * *

This makes speaker attribution exact rather than something a diariser infers, which is the whole * problem with two similar voices on a narrowband line. It is opt-in because whether both sources can * be captured AT ONCE is a property of the device's audio HAL, not of the code; when they cannot, * {@link DualLegCapture#open} returns null and recording falls back to the ordinary mixed source with * nothing lost. Pair it with {@code rec_format=2} (WAV) - a stereo file is only useful if the two * channels survive to the backend intact. */ public static boolean recordingDualLeg() { return "1".equals(resolve("rec_dual_leg", "0")); } private static int intProp(String suffix, int def) { try { String v = resolve(suffix, ""); return v.isEmpty() ? def : Integer.parseInt(v.trim()); } catch (NumberFormatException e) { android.util.Log.w("HelpdeskConfig", "bad numeric property helpdesk." + suffix + ", using default"); return def; } } /** persist.helpdesk. (runtime) → ro.helpdesk. (build) → default. */ private static String resolve(String suffix, String def) { String v = get("persist.helpdesk." + suffix); if (v.isEmpty()) { v = get("ro.helpdesk." + suffix); } return v.isEmpty() ? def : v; } private static String get(String key) { String v = android.os.SystemProperties.get(key, ""); return v == null ? "" : v; } }