package nodomain.freeyourgadget.gadgetbridge.womp;
|
|
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.Context;
|
import android.content.Intent;
|
import android.content.IntentFilter;
|
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.Nullable;
|
import androidx.core.app.NotificationCompat;
|
import androidx.core.content.ContextCompat;
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
import org.json.JSONObject;
|
|
import java.io.Serializable;
|
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
|
|
/**
|
* 前台保活:通知展示手环心率/步数,后台静默接收手环数据并定时上报位置。
|
*/
|
public class WompKeepAliveService extends Service implements LocationListener {
|
private final Handler handler = new Handler(Looper.getMainLooper());
|
private Location lastLocation;
|
private int lastHeartRate;
|
private int lastSteps;
|
private long lastNotifyAt;
|
private long lastAbnormalAt;
|
private boolean locating;
|
private final android.content.BroadcastReceiver sampleReceiver = new android.content.BroadcastReceiver() {
|
@Override
|
public void onReceive(Context context, Intent intent) {
|
if (!DeviceService.ACTION_REALTIME_SAMPLES.equals(intent.getAction())) {
|
return;
|
}
|
Serializable extra = intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE);
|
if (extra instanceof ActivitySample) {
|
ActivitySample sample = (ActivitySample) extra;
|
boolean changed = false;
|
if (sample.getHeartRate() > 0 && sample.getHeartRate() != lastHeartRate) {
|
lastHeartRate = sample.getHeartRate();
|
changed = true;
|
maybeReportAbnormal(sample.getHeartRate());
|
}
|
if (sample.getSteps() > 0) {
|
if (sample.getSteps() >= lastSteps || sample.getSteps() > 200) {
|
lastSteps = sample.getSteps();
|
} else {
|
lastSteps += sample.getSteps();
|
}
|
changed = true;
|
}
|
if (changed) {
|
refreshNotification(false);
|
}
|
}
|
}
|
};
|
|
public static void start(Context context) {
|
Intent i = new Intent(context, WompKeepAliveService.class);
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
context.startForegroundService(i);
|
} else {
|
context.startService(i);
|
}
|
}
|
|
public static void stop(Context context) {
|
context.stopService(new Intent(context, WompKeepAliveService.class));
|
}
|
|
public static void onAttendanceChanged(Context context) {
|
start(context);
|
}
|
|
@Override
|
public void onCreate() {
|
super.onCreate();
|
createChannel();
|
Notification notification = buildNotification();
|
try {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
startForeground(WompConfig.NOTIFY_KEEPALIVE, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
|
} else {
|
startForeground(WompConfig.NOTIFY_KEEPALIVE, notification);
|
}
|
} catch (Exception e) {
|
startForeground(WompConfig.NOTIFY_KEEPALIVE, notification);
|
}
|
IntentFilter filter = new IntentFilter(DeviceService.ACTION_REALTIME_SAMPLES);
|
LocalBroadcastManager.getInstance(this).registerReceiver(sampleReceiver, filter);
|
syncLocationTracking();
|
handler.post(gpsTask);
|
handler.postDelayed(healthTask, 8_000);
|
handler.postDelayed(notifyTask, 15_000);
|
}
|
|
private final Runnable gpsTask = new Runnable() {
|
@Override
|
public void run() {
|
syncLocationTracking();
|
if (WompSession.isCheckedIn(WompKeepAliveService.this)) {
|
reportGps();
|
}
|
handler.postDelayed(this, WompConfig.GPS_INTERVAL_MS);
|
}
|
};
|
|
private final Runnable healthTask = new Runnable() {
|
@Override
|
public void run() {
|
if (WompSession.isCheckedIn(WompKeepAliveService.this)) {
|
reportHealth("周期");
|
}
|
handler.postDelayed(this, WompConfig.HEALTH_INTERVAL_MS);
|
}
|
};
|
|
private final Runnable notifyTask = new Runnable() {
|
@Override
|
public void run() {
|
refreshNotification(true);
|
handler.postDelayed(this, 20_000);
|
}
|
};
|
|
private void syncLocationTracking() {
|
if (WompSession.isCheckedIn(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;
|
}
|
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 (!WompSession.isLoggedIn(this) || !WompSession.isCheckedIn(this) || lastLocation == null) {
|
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, null);
|
} catch (Exception ignored) {
|
}
|
}
|
|
private void reportHealth(String dataType) {
|
if (!WompSession.isLoggedIn(this)) {
|
return;
|
}
|
if (lastHeartRate <= 0 && lastSteps <= 0) {
|
return;
|
}
|
try {
|
JSONObject body = new JSONObject();
|
body.put("sorterId", WompSession.getSorterId(this));
|
if (lastHeartRate > 0) {
|
body.put("heartRate", lastHeartRate);
|
}
|
body.put("stepCount", lastSteps);
|
body.put("dataType", dataType);
|
WompApi.post("/app/push/sorter/health", body, null);
|
} catch (Exception ignored) {
|
}
|
}
|
|
private void maybeReportAbnormal(int heartRate) {
|
if (heartRate < WompConfig.HR_ABNORMAL_LOW || heartRate > WompConfig.HR_ABNORMAL_HIGH) {
|
long now = System.currentTimeMillis();
|
if (now - lastAbnormalAt < WompConfig.ABNORMAL_INTERVAL_MS) {
|
return;
|
}
|
lastAbnormalAt = now;
|
reportHealth("异常");
|
}
|
}
|
|
@Override
|
public void onLocationChanged(Location location) {
|
lastLocation = location;
|
WompLocation.remember(location);
|
}
|
|
@Override
|
public void onStatusChanged(String provider, int status, Bundle extras) {}
|
|
@Override
|
public void onProviderEnabled(String provider) {}
|
|
@Override
|
public void onProviderDisabled(String provider) {}
|
|
@Nullable
|
@Override
|
public IBinder onBind(Intent intent) {
|
return null;
|
}
|
|
@Override
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
syncLocationTracking();
|
return START_STICKY;
|
}
|
|
@Override
|
public void onDestroy() {
|
handler.removeCallbacksAndMessages(null);
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(sampleReceiver);
|
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(boolean force) {
|
long now = System.currentTimeMillis();
|
if (!force && now - lastNotifyAt < 1500) {
|
return;
|
}
|
lastNotifyAt = now;
|
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
nm.notify(WompConfig.NOTIFY_KEEPALIVE, buildNotification());
|
}
|
|
private Notification buildNotification() {
|
Intent open = new Intent(this, ControlCenterv2.class);
|
PendingIntent pi = PendingIntentUtils.getActivity(this, 0, open, 0, false);
|
String hr = lastHeartRate > 0 ? (lastHeartRate + " 次/分") : "--";
|
String steps = lastSteps > 0 ? String.valueOf(lastSteps) : "--";
|
String text = "心率 " + hr + " 步数 " + steps;
|
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();
|
}
|
}
|