package com.jsbogang.womp.workbench; import android.Manifest; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; import androidx.core.content.ContextCompat; import com.jsbogang.womp.android.MainActivity; import com.jsbogang.womp.android.R; import com.jsbogang.womp.ble.VeepooBleManager; import com.jsbogang.womp.health.ActiveMeasureRunner; import com.jsbogang.womp.health.HealthSyncer; import com.jsbogang.womp.health.HealthUploader; import org.json.JSONObject; /** * 前台保活:通知展示心率/步数,定时同步 5 分钟健康数据并上报 GPS。 */ public class WompKeepAliveService extends Service implements LocationListener { private final Handler handler = new Handler(Looper.getMainLooper()); private Location lastLocation; private boolean locating; private long lastGpsUploadAt; public static final String EXTRA_IMMEDIATE_SYNC = "immediateSync"; public static void start(android.content.Context context) { start(context, false); } public static void start(android.content.Context context, boolean immediateSync) { Intent i = new Intent(context, WompKeepAliveService.class); i.putExtra(EXTRA_IMMEDIATE_SYNC, immediateSync); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(i); } else { context.startService(i); } } public static void stop(android.content.Context context) { context.stopService(new Intent(context, WompKeepAliveService.class)); } public static void onAttendanceChanged(android.content.Context context) { start(context); } @Override public void onCreate() { super.onCreate(); HealthSyncer.get().attach(this); HealthUploader.get().attach(this); ActiveMeasureRunner.get().attach(this); VeepooBleManager.get().init(this); createChannel(); Notification notification = buildNotification(); try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { int types = ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; if (Build.VERSION.SDK_INT >= 34) { types |= ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE; } startForeground(WompConfig.NOTIFY_KEEPALIVE, notification, types); } else { startForeground(WompConfig.NOTIFY_KEEPALIVE, notification); } } catch (Exception e) { startForeground(WompConfig.NOTIFY_KEEPALIVE, notification); } syncLocationTracking(); handler.post(gpsTask); handler.postDelayed(syncTask, 15_000); handler.postDelayed(measureTask, 60_000); handler.postDelayed(notifyTask, 15_000); VeepooBleManager.get().connectBound(); } private final Runnable gpsTask = new Runnable() { @Override public void run() { syncLocationTracking(); if (UploadPolicy.shouldUpload(WompKeepAliveService.this)) { reportGps(); } handler.postDelayed(this, WompConfig.GPS_INTERVAL_MS); } }; private final Runnable syncTask = new Runnable() { @Override public void run() { runHealthSync(); handler.postDelayed(this, WompConfig.ORIGIN_SYNC_INTERVAL_MS); } }; private void runHealthSync() { if (VeepooBleManager.get().isReady()) { HealthSyncer.get().refreshLiveStep(); if (!ActiveMeasureRunner.get().isMeasuring()) { HealthSyncer.get().syncToday(); } } else if (DeviceBound()) { VeepooBleManager.get().connectBound(); } if (UploadPolicy.shouldUpload(this)) { HealthUploader.get().flush(); } refreshNotification(); } private final Runnable measureTask = new Runnable() { @Override public void run() { if (VeepooBleManager.get().isReady()) { ActiveMeasureRunner.get().startCycle(); } handler.postDelayed(this, WompConfig.ACTIVE_MEASURE_INTERVAL_MS); } }; private final Runnable notifyTask = new Runnable() { @Override public void run() { refreshNotification(); handler.postDelayed(this, 20_000); } }; private boolean DeviceBound() { return com.jsbogang.womp.ble.DevicePrefs.isBound(this); } private void syncLocationTracking() { if (UploadPolicy.shouldUpload(this)) { startLocation(); } else { stopLocation(); } } private void startLocation() { if (locating) { return; } if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { return; } LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE); boolean started = false; try { lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, WompConfig.GPS_INTERVAL_MS, 5, this, Looper.getMainLooper()); started = true; } catch (Exception ignored) { } try { lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, WompConfig.GPS_INTERVAL_MS, 5, this, Looper.getMainLooper()); started = true; } catch (Exception ignored) { } try { lastLocation = lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); if (lastLocation == null) { lastLocation = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER); } WompLocation.remember(lastLocation); } catch (Exception ignored) { } locating = started; } private void stopLocation() { if (!locating) { return; } LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE); try { lm.removeUpdates(this); } catch (Exception ignored) { } locating = false; } private void reportGps() { if (!UploadPolicy.shouldUpload(this)) { return; } long now = System.currentTimeMillis(); if (lastGpsUploadAt > 0 && now - lastGpsUploadAt < WompConfig.GPS_INTERVAL_MS - 2000) { return; } if (lastLocation == null) { UploadPolicy.debugToast(this, "GPS到点但暂无定位,稍后重试"); return; } try { JSONObject body = new JSONObject(); body.put("sorterId", WompSession.getSorterId(this)); body.put("longitude", lastLocation.getLongitude()); body.put("latitude", lastLocation.getLatitude()); body.put("height", lastLocation.hasAltitude() ? lastLocation.getAltitude() : 0); body.put("speed", lastLocation.hasSpeed() ? lastLocation.getSpeed() : 0); body.put("accuracy", lastLocation.hasAccuracy() ? lastLocation.getAccuracy() : 0); WompApi.post("/app/push/sorter/gps", body, (ok, resp) -> { if (ok) { lastGpsUploadAt = System.currentTimeMillis(); } if (WompConfig.DEBUG_UPLOAD_TOAST) { String msg = ok ? String.format(java.util.Locale.CHINA, "位置已更新 %.5f, %.5f", lastLocation.getLatitude(), lastLocation.getLongitude()) : "位置更新失败"; UploadPolicy.debugToast(WompKeepAliveService.this, msg); } }); } catch (Exception ignored) { } } @Override public void onLocationChanged(@NonNull Location location) { lastLocation = location; WompLocation.remember(location); } @Override @SuppressWarnings("deprecation") public void onStatusChanged(String provider, int status, Bundle extras) {} @Override public void onProviderEnabled(@NonNull String provider) {} @Override public void onProviderDisabled(@NonNull String provider) {} @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { syncLocationTracking(); if (intent != null && intent.getBooleanExtra(EXTRA_IMMEDIATE_SYNC, false)) { runHealthSync(); } if (UploadPolicy.shouldUpload(this)) { reportGps(); } return START_STICKY; } @Override public void onDestroy() { handler.removeCallbacksAndMessages(null); locating = true; stopLocation(); super.onDestroy(); } private void createChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel ch = new NotificationChannel( WompConfig.CHANNEL_KEEPALIVE, "健康手环", NotificationManager.IMPORTANCE_LOW); ch.setDescription("实时显示手环心率和步数"); ch.setShowBadge(false); ch.enableLights(false); ch.enableVibration(false); ch.setSound(null, null); NotificationManager nm = getSystemService(NotificationManager.class); nm.createNotificationChannel(ch); } } private void refreshNotification() { NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); nm.notify(WompConfig.NOTIFY_KEEPALIVE, buildNotification()); } private Notification buildNotification() { Intent open = new Intent(this, MainActivity.class); int flags = PendingIntent.FLAG_UPDATE_CURRENT; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { flags |= PendingIntent.FLAG_IMMUTABLE; } PendingIntent pi = PendingIntent.getActivity(this, 0, open, flags); int hr = HealthUploader.get().latestHeart(); int steps = HealthUploader.get().todaySteps(); String hrText = hr > 0 ? (hr + " 次/分") : "--"; String stepText = steps > 0 ? String.valueOf(steps) : "--"; String text = "心率 " + hrText + " 步数 " + stepText; NotificationCompat.Builder builder = new NotificationCompat.Builder(this, WompConfig.CHANNEL_KEEPALIVE) .setSmallIcon(R.drawable.ic_heart) .setContentTitle("健康手环") .setContentText(text) .setStyle(new NotificationCompat.BigTextStyle().bigText(text)) .setOngoing(true) .setOnlyAlertOnce(true) .setShowWhen(false) .setSilent(true) .setContentIntent(pi) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE); } return builder.build(); } }