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();
|
}
|
}
|