package com.jsbogang.womp.health; import android.content.Context; import android.content.Intent; import android.util.Log; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.jsbogang.womp.ble.AlarmPrefs; import com.jsbogang.womp.ble.DevicePrefs; import com.jsbogang.womp.workbench.UploadPolicy; import com.jsbogang.womp.workbench.WompApi; import com.jsbogang.womp.workbench.WompConfig; import com.jsbogang.womp.workbench.WompSession; import org.json.JSONObject; import java.util.List; public final class HealthUploader { private static final HealthUploader INSTANCE = new HealthUploader(); private static final String TAG = "HealthUploader"; private Context app; private long lastAbnormalAt; private volatile boolean flushing; private HealthUploader() {} public static HealthUploader get() { return INSTANCE; } public void attach(Context context) { app = context.getApplicationContext(); } public void flush() { if (app == null || flushing || !UploadPolicy.shouldUpload(app)) { return; } List pending = AppDatabase.dao().pendingUpload(); if (pending == null || pending.isEmpty()) { return; } flushing = true; uploadNext(pending, 0, pending.size()); } private void uploadNext(List list, int index, int total) { if (index >= list.size()) { flushing = false; if (WompConfig.DEBUG_UPLOAD_TOAST && total > 0) { UploadPolicy.debugToast(app, "健康数据已同步 " + total + " 条"); } return; } HealthSample s = list.get(index); postOne(s, "周期".equals(s.dataType) ? "周期" : s.dataType, () -> { AppDatabase.dao().markUploaded(s.timestamp, s.deviceMac, s.dataType); if ("异常".equals(s.dataType)) { lastAbnormalAt = System.currentTimeMillis(); } else if (AlarmPrefs.isAbnormal(app, s.heartRate, s.bpHigh, s.bpLow, s.spo2)) { long now = System.currentTimeMillis(); if (now - lastAbnormalAt >= WompConfig.ABNORMAL_INTERVAL_MS) { lastAbnormalAt = now; postOne(s, "异常", () -> uploadNext(list, index + 1, total)); return; } } uploadNext(list, index + 1, total); }); } private void postOne(HealthSample s, String dataType, Runnable ok) { try { JSONObject body = new JSONObject(); body.put("sorterId", WompSession.getSorterId(app)); body.put("uploadTime", s.timestamp); if (s.heartRate > 0) { body.put("heartRate", s.heartRate); } long dayStart = HealthSyncer.startOfDay(new java.util.Date(s.timestamp)); body.put("stepCount", HealthSyncer.daySteps(app, s.deviceMac, dayStart, dayStart + 86400000L)); if (s.bpHigh > 0) { body.put("bloodPressureHigh", s.bpHigh); } if (s.bpLow > 0) { body.put("bloodPressureLow", s.bpLow); } if (s.spo2 > 0) { body.put("bloodOxygen", s.spo2); } if (s.temperature > 0) { body.put("temperature", s.temperature); } body.put("dataType", dataType); WompApi.post("/app/push/sorter/health", body, (success, resp) -> { if (success) { if (WompConfig.DEBUG_UPLOAD_TOAST && "异常".equals(dataType)) { UploadPolicy.debugToast(app, "异常健康已同步 心率" + s.heartRate + " 血压" + s.bpHigh + "/" + s.bpLow + " 血氧" + (int) s.spo2); } ok.run(); } else { Log.w(TAG, "upload fail " + resp); flushing = false; } }); } catch (Exception e) { flushing = false; } } public int latestHeart() { if (app == null || !DevicePrefs.isBound(app)) { return 0; } long start = HealthSyncer.startOfDay(new java.util.Date()); HealthSample s = AppDatabase.dao().latestHeart(DevicePrefs.getMac(app), start, start + 86400000L); return s == null ? 0 : s.heartRate; } public int todaySteps() { if (app == null || !DevicePrefs.isBound(app)) { return 0; } long start = HealthSyncer.startOfDay(new java.util.Date()); return HealthSyncer.daySteps(app, DevicePrefs.getMac(app), start, start + 86400000L); } /** * 模拟手表上报一条异常测量,写入本地库并尝试上报服务端。 * @param kind hr / bp / spo2 */ public void simulateAbnormal(String kind) { if (app == null || AppDatabase.get() == null) { return; } String mac = DevicePrefs.isBound(app) ? DevicePrefs.getMac(app) : "SIMULATOR"; HealthSample s = new HealthSample(); s.timestamp = System.currentTimeMillis(); s.deviceMac = mac; s.dataType = "异常"; s.uploaded = 0; s.stepValue = 0; s.calories = 1; s.wear = 1; if ("bp".equals(kind)) { s.heartRate = 88; s.bpHigh = 178; s.bpLow = 112; s.spo2 = 97; } else if ("spo2".equals(kind)) { s.heartRate = 76; s.bpHigh = 118; s.bpLow = 76; s.spo2 = 82; } else { s.heartRate = 168; s.bpHigh = 122; s.bpLow = 78; s.spo2 = 96; } AppDatabase.dao().upsertSample(s); Intent ui = new Intent(HealthSyncer.ACTION_HEALTH); ui.putExtra(HealthSyncer.EXTRA_MSG, "已模拟" + kindLabel(kind) + "异常"); LocalBroadcastManager.getInstance(app).sendBroadcast(ui); if (!UploadPolicy.shouldUpload(app)) { UploadPolicy.debugToast(app, "已模拟入库,当前未在工作时段,暂不同步"); return; } postOne(s, "异常", () -> { AppDatabase.dao().markUploaded(s.timestamp, s.deviceMac, s.dataType); UploadPolicy.debugToast(app, "已模拟" + kindLabel(kind) + "异常"); }); } private static String kindLabel(String kind) { if ("bp".equals(kind)) { return "血压"; } if ("spo2".equals(kind)) { return "血氧"; } return "心率"; } }